美文网首页我爱编程
Java - ClassLoader

Java - ClassLoader

作者: 齐晋 | 来源:发表于2018-06-10 07:43 被阅读35次

    有哪些ClassLoader

    • Bootstrap ClassLoader
    • Extension ClassLoader
    • App ClassLoader

    Bootstrap ClassLoader

    • 类名:BootstrapClassLoader
    • Bootstrap ClassLoader不是Java Class(其他的ClassLoader,包括Extension ClassLoader都是Java类),而是JVM虚拟机中实现的类加载器,用C或者C++写的。
    • 负责加载$JAVA_HOME/jre/lib/目录下的class,如$JAVA_HOME/jre/lib/rt.jar。这些classes可以称为"Bootstrap classes"
    • 通过非标准参数-Xbootclasspath可以改变Bootstrap classes的位置。但是绝大部分情况下,是没必要的

    Extension ClassLoader

    • 类名:ExtClassLoader,继承自ClassLoader
    • 加载$JAVA_HOME/jre/lib/ext/目录下的class
    • 如果ext目录下的jar包中有相同路径的class,那么这个class是没法被加载的

    如:

    smart-extension1_0.jar 有class: smart.extension.Smart
    smart-extension1_1.jar 同样有class: smart.extension.Smart
    那么,smart.extension.Smart是undefined

    image.png

    App ClassLoader

    • 类名:AppClassLoader,父ClassLoader是ExtClassLoader
    • 加载classpath中的class,或者是java.class.path属性定义目录下的class。
    • java.class.path的默认值是'.',也就是当前目录
    image.png

    如果com.mypackage.MyClass在/classes/目录下,那么/classes/目录必须在classpath中。如果该class在myclasses.jar中,那么myclasses.jar也必须在classpath中。

    那么,classpath如何指定呢?

    • 默认是".",也就是当前目录
    • 设置CLASSPATH环境变量
    • 通过-cp或者-classpath参数指定。这种方式会覆盖默认值和CLASSPATH设定的值
    • 通过-jar参数指定。这个会覆盖其他所有的值。

    如:

    java -jar spring-boot.jar

    没有通过-cp/-classpath指定classpath,也没有-Djava.class.path参数,因此,默认是当前目录,也就是加载spring-boot.jar中的class

    查看ClassLoader

    Class类中提供了getClassLoader()方法来查看某个类的ClassLoader。简单的例子如下:

    public class App 
    {
        public static void main( String[] args ) {
            System.out.println("App's ClassLoader is: " + App.class.getClassLoader());
            ClassLoader parent = App.class.getClassLoader().getParent();
            System.out.println("AppClassLoader's parent is:" + parent);
    
            InnerClass innerClass = new InnerClass();
            System.out.println("InnerClass's ClassLoader is:" + innerClass.getClass().getClassLoader());
        }
    }
    

    输出为:

    App's ClassLoader is: sun.misc.Launcher$AppClassLoader@279f2327
    AppClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@452b3a41
    InnerClass's ClassLoader is:sun.misc.Launcher$AppClassLoader@279f2327
    

    Java类加载机制

    英文术语

    Java类加载机制:Java Class Loading Mechanism
    父ClassLoader: parent class loader

    所有自定义的ClassLoader都继承自java.lang.ClassLoaderjava.lang.ClassLoader的构造函数允许指定父ClassLoader。如果不指定,则使用system class loader。

    public abstract class ClassLoader {
        //parent class loader默认为system class loader
        protected ClassLoader() {
            this(checkCreateClassLoader(), getSystemClassLoader());
        }
        //当然,也可以指定parent class loader
        protected ClassLoader(ClassLoader parent) {
            this(checkCreateClassLoader(), parent);
        }
    }
    

    class在loadClass(String name)方法中被加载。加载逻辑是:

    1. 如果class已经被加载了,直接return
    2. 如果class还没被加载,让父ClassLoader去加载。当然,如果父ClassLoader也有parent,则会继续把加载任务交给其父ClassLoader(就是让爸爸的爸爸去实际干活)。
    3. 如果父ClassLoader找不到class,则自定义的ClassLoader才开始加载class。
    4. 如果都找不到,抛出ClassNotFoundException

    这就是所谓的“双亲委托加载模型”。

    源代码为:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
    
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
    
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    

    为什么要委托给parent ClassLoader加载呢?
    这是因为,一个Class在JVM中,是通过而元素来标识的,一个是ClassLoader,一个是类自己的全路径类名。
    举个例子,有个类com.my.TestClass和一个自定义的加载器TestClassLoader(如不指定父ClassLoader,那么其parent为AppClassLoader)

    • 如果TestClassTestClassLoader加载,这标识为<TestClassLoader, TestClass>
    • 如果TestClassAppClassLoader加载,这标识为<AppClassLoader, TestClass>

    那么问题来了,同一个类com.my.TestClass由不同的ClassLoader加载,对于JVM来讲,是不同的类

    假如有类似如下代码:

    TestClass A = ...;  //由TestClassLoader加载
    TestClass B = ...;  //由AppClassLoader加载
    
    A = B;  //报错!抛出ClassCastException
    

    上面的类由不同的ClassLoader加载,执行到A=B时,会出现类似如下报错:

    java.lang.ClassCastException: com.my.TestClass can not be cast to com.my.TestClass
    

    因此,同一个Class由同一个ClassLoader加载是非常必要的。优先由parent ClassLoader加载就可以避免上述问题。

    什么时候加载Class

    参见:Java - 什么时候加载class

    参考

    相关文章

      网友评论

        本文标题:Java - ClassLoader

        本文链接:https://www.haomeiwen.com/subject/soffeftx.html