另一篇比较好的讲述classloader的文章[转载]

    技术2022-05-14  1

    java classLoader 体系结构

    发表于: 2009年6月29日 | 分类: java | 标签: architecture, classloader | views(9,537)

    版权信息: 可以任意转载, 转载时请务必以超链接形式标明文章原文出处, 即下面的声明.

     

    原文出处:http://blog.chenlb.com/2009/06/java-classloader-architecture.html

    jvm classLoader architecture:

    Bootstrap ClassLoader/启动类加载器 主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。Extension ClassLoader/扩展类加载器 主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。System ClassLoader/系统类加载器 主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类) 在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。

    类加载器的特性:

    每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。

    classloader-architecture

    classloader-class-diagram

    类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为,它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。

    自定义类加载器加载一个类的步骤

    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);        }   }   // 检查类是否已被装载过 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);   // 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)。

    为什么要使用这种双亲委托模式呢?

    因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

    java动态载入class的两种方式:

    implicit隐式,即利用实例化才载入的特性来动态载入classexplicit显式方式,又分两种方式: java.lang.Class的forName()方法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;   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类时不成功则抛出这个异常

    参考:

    重温java之classloader体系结构(含hotswap)classloader相关基础知识

    最新回复(0)