`

ClassLoader详解

 
阅读更多

Point One
 
将J2EE应用程序移植到WebSphere应用程序服务器
 
 
Point Two
类加载器的种类:
  1. Bootstrap ClassLoader/启动类加载器 
    主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
  2. Extension ClassLoader/扩展类加载器 
    主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
  3. System ClassLoader/系统类加载器 
    主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
  4. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类) 
    在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
类加载器的特性:
  1. 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
  2. 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。
classloader-architecture
classloader-architecture
classloader-class-diagram
classloader-class-diagram
类图中,BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为,它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。
 
自定义类加载器加载一个类的步骤
classloader-load-class
classloader-load-class
 
ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:
// 检查类是否已被装载过        
Class c = findLoadedClass(name);        
if (c == null ) {        
         // 指定类未被装载过        
         try {        
                 if (parent != null ) {        
                         // 如果父类加载器不为空, 则委派给父类加载        
                         c = parent.loadClass(name, false );        
                 } else {        
                         // 如果父类加载器为空, 则委派给启动类加载加载        
                         c = findBootstrapClass0(name);        
                 }        
         } catch (ClassNotFoundException e) {        
                 // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其        
                 // 捕获, 并通过findClass方法, 由自身加载        
                 c = findClass(name);        
         }        
}
 
线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader)。
// Now create the class loader to use to launch the application        
try {        
        loader = AppClassLoader.getAppClassLoader(extcl);        
} catch (IOException e) {        
        throw new InternalError(        
"Could not create application class loader" );        
}         
     
// Also set the context class loader for the primordial thread.        
Thread.currentThread().setContextClassLoader(loader);    
 
以上代码摘自sun.misc.Launch的无参构造函数Launch()。
使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).
线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.
随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择。
当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException)。
 
为什么要使用这种双亲委托模式呢?
  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
java动态载入class的两种方式:
  1. implicit隐式,即利用实例化才载入的特性来动态载入class
  2. explicit显式方式,又分两种方式:
    1. java.lang.Class的forName()方法
    2. java.lang.ClassLoader的loadClass()方法
用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的。
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰。
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的。
public static Class forName(String className)        
         throws ClassNotFoundException {        
         return forName0(className, true , ClassLoader.getCallerClassLoader());        
}         
     
/** Called after security checks have been made. */     
private static native Class forName0(String name, boolean initialize,        
ClassLoader loader)        
         throws ClassNotFoundException;    
上面中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器
static块在什么时候执行?
  • 当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
  • 如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
  • static块仅执行一次
各个java类由哪些classLoader加载?
  • java类可以通过实例.getClass.getClassLoader()得知
  • 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
  • ClassLoader类由bootstrap loader载入
NoClassDefFoundError和ClassNotFoundException
  • NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
  • ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
Point Three
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。
一.    ClassLoader基本概念
1.ClassLoader分类
类装载器是用来把类(class)装载进JVM的。
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader) 


JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。
AppClassLoaderParentExtClassLoader,而ExtClassLoaderParentBootstrap ClassLoader
 
Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。
 
例1,测试你所使用的JVM的ClassLoader
/*LoaderSample1.java*/
public     class    LoaderSample1 { 
         public     static     void    main(String[] args) { 
                Class c; 
                ClassLoader cl; 
                cl    =    ClassLoader.getSystemClassLoader(); 
                System.out.println(cl); 
                 while    (cl    !=     null ) { 
                        cl    =    cl.getParent(); 
                        System.out.println(cl); 
                } 
                 try    { 
                        c    =    Class.forName( " java.lang.Object " ); 
                        cl    =    c.getClassLoader(); 
                        System.out.println( " java.lang.Object's loader is    "     +    cl); 
                        c    =    Class.forName( " LoaderSample1 " ); 
                        cl    =    c.getClassLoader(); 
                        System.out.println( " LoaderSample1's loader is    "     +    cl); 
                }    catch    (Exception e) { 
                        e.printStackTrace(); 
                } 
        } 
}
在我的机器上(Sun Java 1.4.2)的运行结果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null 
java.lang.Object's loader is null
LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f

第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader 
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader 
第三行表示,系统类装载器parent的parent为bootstrap 
第四行表示,核心类java.lang.Object是由bootstrap装载的 
第五行表示,用户类LoaderSample1是由系统类装载器装载的 
 
 
二.parent delegation模型

从1.2版本开始,Java引入了双亲委托模型,从而更好的保证Java平台的安全。在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载

图 1 parent delegation模型
如图1所示,loader2的parent为loader1,loader1的parent为system class loader。假设loader2被要求装载类MyClass,在parent delegation模型下,loader2首先请求loader1代为装载,loader1再请求系统类装载器去装载MyClass。若系统装载器能成功装载,则将MyClass所对应的Class对象的reference返回给loader1,loader1再将reference返回给loader2,从而成功将类MyClass装载进虚拟机。若系统类装载器不能装载MyClass,loader1会尝试装载MyClass,若loader1也不能成功装载,loader2会尝试装载。若所有的parent及loader2本身都不能装载,则装载失败。
 
若有一个能成功装载,实际装载的类装载器被称为定义类装载器,所有能成功返回Class对象的装载器(包括定义类装载器)被称为初始类装载器。如图1所示,假设loader1实际装载了MyClass,则loader1为MyClass的定义类装载器,loader2和loader1为MyClass的初始类装载器。
 
需要指出的是,Class Loader是对象,它的父子关系和类的父子关系没有任何关系。
 
那么parent delegation模型为什么更安全了?因为在此模型下用户自定义的类装载器不可能装载应该由父亲装载器装载的可靠类,从而防止不可靠甚至恶意的代码代替由父亲装载器装载的可靠代码。实际上,类装载器的编写者可以自由选择不用把请求委托给parent,但正如上所说,会带来安全的问题。

 
 
三.命名空间及其作用

每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。
 
例2演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以访问LoaderSampl3中公共的成员(如age)。
例2不同命名空间的类的访问
/*LoaderSample2.java*/
import    java.net. * ; 
import    java.lang.reflect. * ; 
public     class    LoaderSample2 { 
         public     static     void    main(String[] args) { 
                 try    { 
                        String path    =    System.getProperty( " user.dir " ); 
                        URL[] us    =    { new    URL( 
" file:// "     +    path    +     " /sub/ " )}; 
                        ClassLoader loader    =     new    URLClassLoader(us); 
                        Class c    =    loader.loadClass( " LoaderSample3 " ); 
                        Object o    =    c.newInstance(); 
                        Field f    =    c.getField( " age " ); 
                         int    age    =    f.getInt(o); 
                        System.out.println( " age is    "     +    age); 
                }    catch    (Exception e) { 
                        e.printStackTrace(); 
                } 
        } 
}
/*sub/Loadersample3.java*/
public     class    LoaderSample3 { 
         static    { 
                System.out.println( " LoaderSample3 loaded " ); 
        } 
         public     int    age    =     30 ; 
}
编译:javac LoaderSample2.java; javac sub/LoaderSample3.java
运行:java LoaderSample2
LoaderSample3 loaded
age is 30
从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以访问其公共成员age。
运行时包(runtime package)
由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.*由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能访问核心类库java.lang中类的包可见的成员。 
 
总结

命名空间并没有完全禁止属于不同空间的类的互相访问,双亲委托模型加强了Java的安全,运行时包增加了对包可见成员的保护。
 
二.    扩展ClassLoader方法

我们目的是从本地文件系统使用我们实现的类装载器装载一个类。为了创建自己的类装载器我们应该扩展ClassLoader类,这是一个抽象类。我们创建一个FileClassLoader extends ClassLoader。我们需要覆盖ClassLoader中的findClass(String name)方法,这个方法通过类的名字而得到一个Class对象。
         public    Class findClass(String name) 
        { 
                 byte [] data    =    loadClassData(name); 
                 return    defineClass(name, data,    0 , data.length); 
        }
我们还应该提供一个方法loadClassData(String name),通过类的名称返回class文件的字 
节数组。然后使用ClassLoader提供的defineClass()方法我们就可以返回Class对象了。
public     byte [] loadClassData(String name) 
        { 
                FileInputStream fis    =     null ; 
                 byte [] data    =     null ; 
                 try    
                { 
                        fis    =     new    FileInputStream( new    File(drive    +    name    +    fileType)); 
                        ByteArrayOutputStream baos    =     new    ByteArrayOutputStream(); 
                         int    ch    =     0 ; 
                         while    ((ch    =    fis.read())    !=     - 1 ) 
                        { 
                                baos.write(ch); 
                                
                        } 
                        data    =    baos.toByteArray(); 
                }    catch    (IOException e) 
                { 
                        e.printStackTrace(); 
                } 
                 
                 return    data; 
        }
 

本文出自 “专注J2EE系列规范下的..” 博客,请务必保留此出处http://danni505.blog.51cto.com/15547/227437

分享到:
评论

相关推荐

    Java_ClassLoader详解

    Java_ClassLoader详解,解说java类的加载的原理,让你轻松了解java的类加载

    ClassLoader 详解.doc

    关于J2EE服务器的ClassLoader的原理,该文档清晰了揭示了jvm装载类的顺序,同时用户可以自定义修改classLoader的配置 通过该文档,可以加深对Java虚拟机的理解

    ClassLoader类加载机制和原理详解

    ClassLoader类加载机制和原理详解

    ClassLoader机制详解

    由osgi引出的classLoader的大总结(整理理解ClassLoader)

    详解Android类加载ClassLoader

    本篇文章主要介绍了详解Android类加载ClassLoader,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    classloader类加载器_基于java类的加载方式详解

    下面小编就为大家带来一篇classloader类加载器_基于java类的加载方式详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    Java文件路径详解

    Java 文件路径详解是 Java 中获取文件路径的方式,包括 Class.getResourceAsStream() 和 ClassLoader.getResourceAsStream() 两种方法。这些方法可以帮助开发者快速获取文件路径,以便于读取文件的内容。

    Java classloader和namespace详细介绍

    主要介绍了Java classloader和namespace详细介绍的相关资料,需要的朋友可以参考下

    java ClassLoader机制详细讲解

    ClassLoader一个经常出现又让很多人望而却步的词,本文将试图以最浅显易懂的方式来讲解 ClassLoader,希望能对不了解该机制的朋友起到一点点作用

    Java运行时环境之ClassLoader类加载机制详解

    主要给大家介绍了关于Java运行时环境之ClassLoader类加载机制的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    Java虚拟机工作原理详解

    * User-Defined Class Loader:这是开发人员通过拓展 ClassLoader 类定义的自定义加载器,加载程序员定义的一些类。 类加载器之间存在一种委派模式(Delegation Mode),当 JVM 加载一个类的时候,下层的加载器会将...

    Java高并发编程详解:多线程与架构设计 (Java核心技术系列)

    第二部分引入了ClassLoader,这是因为ClassLoader与线程不无关系,我们可以通过synchronized关键字,或者Lock等显式锁的方式在代码的编写阶段对共享资源进行数据一致性保护,那么一个Class在完成初始化的整个过程到...

    Java字节码(.class文件)格式详解((转载)

    NULL 博文链接:https://plkong.iteye.com/blog/1680902

    单例模式详解

    Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论、涉及到多个类加载器(ClassLoader)协同时、涉及到跨JVM(集群、远程EJB等)时、涉及到单例对象...

    详解Android中实现热更新的原理

    我们知道Java在运行时加载对应的类是通过ClassLoader来实现的,ClassLoader本身是一个抽象来,Android中使用PathClassLoader类作为Android的默认的类加载器,PathClassLoader其实实现的就是简单的从文件系统中加载类...

    汪文君高并发编程实战视频资源下载.txt

     高并发编程第三阶段07讲 AtomicReference详解,CAS算法带来的ABA问题详解.mp4  高并发编程第三阶段08讲 AtomicStampReference详解,解决CAS带来的ABA问题.mp4  高并发编程第三阶段09讲 AtomicIntegerArray,...

    Java中Class类工作原理详解

    类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(恰当地说,是被保存在一个同名的.class文件中)。

    Java 类加载机制详解

     类加载器(ClassLoader),顾名思义,即加载类的东西。在我们使用一个类之前,JVM需要先将该类的字节码文件(.class文件)从磁盘、网络或其他来源加载到内存中,并对字节码进行解析生成对应的Class对象,这是类...

    详解Java内存泄露的示例代码

    通过一个Demo来简要介绍下ThreadLocal和ClassLoader导致内存泄露最终OutOfMemory的场景。下面通过示例代码给大家分享Java内存泄露的相关知识,感兴趣的朋友一起看看吧

Global site tag (gtag.js) - Google Analytics