• 类加载器


    本博客将沿用上篇博客中展示的自定义类加载器代码

    复杂类加载情况分析

    测试代码一

    首先,新建一个类Test14,重写默认的构造方法,打印加载该类的类加载器

    public class Test14 {
        public Test14() {
            System.out.println("Test14 is loaded by:" + this.getClass().getClassLoader());
        }
    }
    

    然后,在新建一个类Test15,同样重写默认的构造方法,打印加载该类的类加载器,在构造方法中new出Test14的实例

    public class Test15 {
        public Test15() {
            System.out.println("Test15 is loaded by:" + this.getClass().getClassLoader());
    
            new Test14();
        }
    }
    

    测试代码

    public class Test16 {
        public static void main(String[] args) throws Exception {
            test01();
        }
    
        private static void test01 () throws Exception {
            ClassLoaderTest classLoader = new ClassLoaderTest("classLoader");
            Class<?> clazz = classLoader.loadClass("classloader.Test15");
            System.out.println("class:" + clazz);
            Object object = clazz.newInstance();
        }
    }
    

    猜测一下,首先自定义类加载器classLoader通过反射获取Test15的Class对象,属于主动使用,会加载Test15,classLoader委托它的父加载器AppClassLoader加载Test15;然后我们通过 clazz.newInstance();代码获取Test15的实例,调用Test15的构造方法,在Test15的构造方法中创建了Test14的实例,所以同样加载了Test14,并调用了Test14的构造方法。加上-XX:+TraceClassLoading指令执行代码,发现运行结果和我们想的是一样的。

    ......
    [Loaded classloader.Test15 from file:/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/]
    class:class classloader.Test15
    Test15 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    [Loaded classloader.Test14 from file:/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/]
    Test14 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    ......
    

    测试代码二

    在上篇博客中,自定义类加载器ClassLoaderTest是有一个path属性可以自定义类的加载路径的,我们同样测试一下,我们将Test14和Test15的class文件放到桌面的classloader文件夹下,然后删除工程路径下的class文件,执行一下的测试代码

    public class Test16 {
        public static void main(String[] args) throws Exception {
            test02();
        }
        private static void test02 () throws Exception {
            ClassLoaderTest classLoader = new ClassLoaderTest("classLoader");
            classLoader.setPath("/home/fanxuan/桌面/");
            Class<?> clazz = classLoader.loadClass("classloader.Test15");
            System.out.println("class:" + clazz);
            Object object = clazz.newInstance();
        }
    }
    

    按照上节的结果,应该都是ClassLoaderTest加载器加载了Test14和Test15类

    class:class classloader.Test15
    Test15 is loaded by:classloader.ClassLoaderTest@6d6f6e28
    Test14 is loaded by:classloader.ClassLoaderTest@6d6f6e28
    

    接下来,我们重新编译项目,删除掉工程目录下的Test14的calss文件,再次执行代码

    class:class classloader.Test15
    Test15 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    Exception in thread "main" java.lang.NoClassDefFoundError: classloader/Test14
    	at classloader.Test15.<init>(Test15.java:11)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    	at java.lang.Class.newInstance(Class.java:442)
    	at classloader.Test16.test02(Test16.java:25)
    	at classloader.Test16.main(Test16.java:9)
    Caused by: java.lang.ClassNotFoundException: classloader.Test14
    	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    	... 8 more
    

    我们发现结果报错了,按照我们正常的思维,自定义记载器classLoader委托父加载器AppClassLoader加载Test15,从打印结果可以看出Test15加载成功了,然后创建Test15的实例,加载Test14,因为工程目录下缺少Test14的class文件,所以AppClassLoader无法加载到Test14,由自定义加载器classLoader自身从桌面加载Test14。但是我们发现加载Test14的报了ClassNotFoundException的错误,这是因为在Test15中记载Test14的时候,是以Test15的类加载器AppClassLoader来加载的,AppClassLoader加载不到Test14,它的父加载器扩展类加载器同样加载不到,扩展类加载器的父加载器启动类加载器也加载不到,所以报错ClassNotFoundException

    然后,再重新编译项目,删除掉工程目录下的Test15的calss文件,再次执行代码。根据前文分析的代码,我们可以很清晰的得出结论:由自定义记载器classLoader加载了Test15,由系统类记载器AppClassLoader加载了Test14。

    class:class classloader.Test15
    Test15 is loaded by:classloader.ClassLoaderTest@6d6f6e28
    Test14 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    

    测试代码三

    简单修改下Test14类,在Test14的构造方法中引用Test15的Class对象。

    public class Test14 {
        public Test14() {
            System.out.println("Test14 is loaded by:" + this.getClass().getClassLoader());
    
            System.out.println("Test14:" + Test15.class);
        }
    }
    

    执行测试代码二中的测试代码Test16,结果如下,没有任何问题。

    class:class classloader.Test15
    Test15 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    Test14 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    Test14:class classloader.Test15
    

    我们同样重新编译项目,删除掉工程目录下的Test15的calss文件,再次执行代码。

    class:class classloader.Test15
    Test15 is loaded by:classloader.ClassLoaderTest@6d6f6e28
    Test14 is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
    Exception in thread "main" java.lang.NoClassDefFoundError: classloader/Test15
    	at classloader.Test14.<init>(Test14.java:11)
    	at classloader.Test15.<init>(Test15.java:11)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    	at java.lang.Class.newInstance(Class.java:442)
    	at classloader.Test16.test02(Test16.java:25)
    	at classloader.Test16.main(Test16.java:9)
    Caused by: java.lang.ClassNotFoundException: classloader.Test15
    	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    	... 9 more
    

    我们发现加载已经完成了,但是程序还是报错了,是我们刚刚加的 System.out.println("Test14:" + Test15.class);代码报的错,依然是ClassNotFoundException错误。

    分析:
    Test15由自定义记载器classLoader加载,Test14由系统类记载器AppClassLoader加载。导致程序报错的是因为命名空间的问题,我们在上一篇博客的结尾简单介绍了命名空间:每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成。子加载器所加载的类可以看见父加载器加载的类,但是父加载器所加载的类无法看见子加载器加载的类。Test14是由AppClassLoader加载的,在AppClassLoader的命名空间中没有Test15的,所以程序报错了。

    命名空间实例分析

    测试代码

    新建Entity类用于测试

    public class Entity {
        private Entity entity;
    
        public void setEntity(Object entity) {
            this.entity = (Entity)entity;
        }
    }
    

    编写测试代码

    public class Test17 {
        public static void main(String[] args) throws Exception {
            ClassLoaderTest classLoader1 = new ClassLoaderTest("classLoader1");
            ClassLoaderTest classLoader2 = new ClassLoaderTest("classLoader2");
    
            Class<?> clazz1 = classLoader1.loadClass("classloader.Entity");
            Class<?> clazz2 = classLoader2.loadClass("classloader.Entity");
    
            System.out.println(clazz1 == clazz2);
    
            Object object1 = clazz1.newInstance();
            Object object2 = clazz2.newInstance();
    
            Method method = clazz1.getMethod("setEntity", Object.class);
            method.invoke(object1, object2);
        }
    }
    

    运行程序,System.out.println(clazz1 == clazz2);返回结果为true,都是AppClassLoader加载的,classLoader1加载之后会在AppClassLoader的命名空间中形成缓存,classLoader2加载的时候直接返回命名空间已经存在的Class对象,所以clazz1与clazz2相同。

    改造下代码,将Entity类的class文件copy到桌面文件夹下,删除工程下的class文件,执行如下代码

    public class Test18 {
        public static void main(String[] args) throws Exception {
            ClassLoaderTest classLoader1 = new ClassLoaderTest("classLoader1");
            ClassLoaderTest classLoader2 = new ClassLoaderTest("classLoader2");
    
            classLoader1.setPath("/home/fanxuan/桌面/");
            classLoader2.setPath("/home/fanxuan/桌面/");
    
            Class<?> clazz1 = classLoader1.loadClass("classloader.Entity");
            Class<?> clazz2 = classLoader2.loadClass("classloader.Entity");
    
            System.out.println(clazz1 == clazz2);
    
            Object object1 = clazz1.newInstance();
            Object object2 = clazz2.newInstance();
    
            Method method = clazz1.getMethod("setEntity", Object.class);
            method.invoke(object1, object2);
        }
    }
    

    根据前文的介绍,不难推断System.out.println(clazz1 == clazz2);的运行结果为falseclassLoader1和classLoader2分别加载了Entity类,就是其自身加载的(定义类加载器),在jvm的内存中形成了完全独立的两个命名空间,所以clazz1与clazz2不同。而且因为clazz1和clazz2相互不可见,调用了classLoader1命名空间中的方法,传入了classLoader2命名空间的对象,导致程序抛出了异常。

    false
    Exception in thread "main" java.lang.reflect.InvocationTargetException
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at classloader.Test18.main(Test18.java:26)
    Caused by: java.lang.ClassCastException: classloader.Entity cannot be cast to classloader.Entity
    	at classloader.Entity.setEntity(Entity.java:11)
    	... 5 more
    

    不同类加载器的命名空间关系

    • 同一命名空间内的类是相互可见的
    • 子加载器的命名空间包含所有父加载器的命名空间,由子加载器所加载的类可以看见父加载器加载的类
    • 由父加载器所加载的类无法看见子加载器加载的类
    • 如果两个加载器之间没有任何直接或间接的父子关系,那么它们各自加载的类相互不可见

    父亲委托机制的好处

    上篇博客的2.1章节简单介绍了一下类加载器的父亲委托机制,这里面来总结一下好处

    • 确保Java核心类库的安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被记载到Java虚拟机当中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么可能会在JVM中存在多个版本的java.lang.Object类,而且这些类还是不兼容的、相互不可见的(因为命名空间的原因)。借助父亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成的,从而确保了Java应用所使用的都是同一个版本的Java核心类库,他们之间是互相兼容的。
    • 确保Java核心类库提供的类不会被自定义的类所替代。
    • 不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加他们即可,不同类加载器所加载的类是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间。
  • 相关阅读:
    图论算法 有图有代码 万字总结 向前辈致敬
    关闭和打开键盘的通知
    (copy)赋值构造函数的4种调用时机or方法
    构造函数的分类
    Uva
    Uva
    The 2018 ACM-ICPC Asia Qingdao Regional Contest F
    The 2018 ACM-ICPC Asia Qingdao Regional Contest E Plants vs. Zombies(ZOJ 4062)
    K Color Graph
    Cow and Fields
  • 原文地址:https://www.cnblogs.com/fx-blog/p/11917459.html
Copyright © 2020-2023  润新知