父子类加载器的命名空间

类A中引用了类B,那么加载类B时,由类A的类加载器按照双亲委托机制尝试加载。子类加载器加载的类可以引用父类加载器加载的类,而父类加载器加载的类不能引用子类加载器加载的类。

测试案例如下: image

MyClassLoader.java:

 1package io.dongzhi;
 2import java.io.*;
 3public class MyClassLoader extends ClassLoader {
 4    private final String ext = ".class";
 5    private String path;
 6
 7    public MyClassLoader(String path) {
 8        this.path = path;
 9    }
10    public MyClassLoader(String path, ClassLoader parent) {
11        super(parent);
12        this.path = path;
13
14    }
15    @Override
16    public String toString() {
17        return super.toString() + System.currentTimeMillis();
18    }
19
20
21    @Override
22    protected Class<?> findClass(String name) {
23        String fullPath = this.path + "/" + name.replace(".", "/") + ext;
24        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
25             FileInputStream fis = new FileInputStream(new File(fullPath));) {
26            byte[] buffer = new byte[512];
27            int count = 0;
28            while ((count = fis.read(buffer)) != -1) {
29                bos.write(buffer, 0, count);
30            }
31            return super.defineClass(name, bos.toByteArray(), 0, bos.size());
32        } catch (FileNotFoundException e) {
33            e.printStackTrace();
34        } catch (IOException e) {
35            e.printStackTrace();
36        }
37        return null;
38    }
39
40}

Person.java:

 1package io.dongzhi;
 2
 3public class Person {
 4    private Student student = new Student();
 5
 6    public Person(Student student) {
 7        this.student = student;
 8    }
 9
10    public Person() {
11        System.out.println("person constructor invoked!");
12        System.out.println("person class loader:" + this.getClass().getClassLoader());
13    }
14}

Student.java:

1package io.dongzhi;
2
3public class Student {
4    public Student() {
5        System.out.println("Student constructor invoked!");
6        System.out.println("Student class loader:" + this.getClass().getClassLoader());
7    }
8}

APP.java:

 1package io.dongzhi;
 2
 3/**
 4 * Hello world!
 5 *
 6 */
 7public class App {
 8    public static void main( String[] args ) throws Exception {
 9        MyClassLoader loader1 = new MyClassLoader("D:\\dongzhi\\Desktop");
10        Class clazz = loader1.loadClass("io.dongzhi.Person");
11        Object instance = clazz.newInstance();
12
13    }
14}

执行main方法,结果如下:

1Student constructor invoked!
2Student class loader:sun.misc.Launcher$AppClassLoader@18b4aac2
3person constructor invoked!
4person class loader:sun.misc.Launcher$AppClassLoader@18b4aac2
5
6Process finished with exit code 0

可见,加载Person和Student的类加载器都是系统类加载器。 将Perosn.class和Student.class剪切到D:/dongzhi/Desktop/io/dongzhi/目录,执行结果如下:

1Student constructor invoked!
2Student class loader:io.dongzhi.MyClassLoader@677327b61578062529660
3person constructor invoked!
4person class loader:io.dongzhi.MyClassLoader@677327b61578062529660
5
6Process finished with exit code 0

这时候,类加载器都是MyClassLoader。删除项目target目录的Person.class,保留Student.class文件。此时的输出结果是:

1Student constructor invoked!
2Student class loader:sun.misc.Launcher$AppClassLoader@18b4aac2
3person constructor invoked!
4person class loader:io.dongzhi.MyClassLoader@677327b61578062873859
5
6Process finished with exit code 0

项目类路径没有Person类,所以由自定义类加载器加载,该类加载器接着尝试去加载Student类,它首先委托给父类加载器AppClassLoader加载,显然可以加载成功。所以,Person的类加载器是MyClassLoader,Student的类加载器是AppClassLoader。

修改Student类,引用Person类:

1package io.dongzhi;
2
3public class Student {
4    public Student() {
5        System.out.println("Student constructor invoked!");
6        System.out.println("Student class loader:" + this.getClass().getClassLoader());
7        System.out.println("Student invoke Person:" + Person.class);// 引用Person**
8    }
9}

由于Student的类加载器是Person类加载器的父类加载器,高层级类命名空间中类无法访问下一层类加载器空间的类,所以Student引用Person类时会报找不到该类异常。此时输出结果:

 1Student constructor invoked!
 2Student class loader:sun.misc.Launcher$AppClassLoader@18b4aac2
 3Exception in thread "main" java.lang.NoClassDefFoundError: io/dongzhi/Person
 4	at io.dongzhi.Student.<init>(Student.java:7)
 5	at io.dongzhi.Person.<init>(Person.java:4)
 6	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 7	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 8	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 9	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
10	at java.lang.Class.newInstance(Class.java:442)
11	at io.dongzhi.App.main(App.java:11)
12Caused by: java.lang.ClassNotFoundException: io.dongzhi.Person
13	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
14	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
15	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
16	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
17	... 8 more
18
19Process finished with exit code 1

如果在Person类中引用Student类,则不会报异常:

 1package io.dongzhi;
 2
 3public class Person {
 4    private Student student = new Student();
 5
 6    public Person(Student student) {
 7        this.student = student;
 8    }
 9
10    public Person() {
11        System.out.println("person constructor invoked!");
12        System.out.println("person class loader:" + this.getClass().getClassLoader());
13        System.out.println("Person invoke Student:" + Student.class);// 引用Student
14    }
15}

控制台输出:

1Student constructor invoked!
2Student class loader:sun.misc.Launcher$AppClassLoader@18b4aac2
3person constructor invoked!
4person class loader:io.dongzhi.MyClassLoader@677327b61578065044711
5Person invoke Student:class io.dongzhi.Student
6
7Process finished with exit code 0

到这里,本文开头的结论测试完毕。

自带类加载器的路径

通过三个系统参数可以获取:

  • sun.boot.class.path 根类加载器路径
  • java.ext.dirs 扩展类加载器路径
  • java.class.path系统类加载器的路径
 1package io.dongzhi;
 2
 3import java.util.Arrays;
 4
 5public class TestPath {
 6    public static void main(String[] args) {
 7        System.out.println("Bootstrap Class Loader加载路径:"  );
 8        Arrays.asList(System.getProperty("sun.boot.class.path").split(";")).forEach(System.out::println);
 9        System.out.println("Ext Class Loader加载路径:");
10        Arrays.asList(System.getProperty("java.ext.dirs").split(";")).forEach(System.out::println);
11        System.out.println("App ClassLoader加载路径:" );
12        Arrays.asList(System.getProperty("java.class.path").split(";")).forEach(System.out::println);
13    }
14}

打印的路径结果:

 1Bootstrap Class Loader加载路径:
 2C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar
 3C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
 4C:\Program Files\Java\jdk1.8.0_131\jre\lib\sunrsasign.jar
 5C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar
 6C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar
 7C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar
 8C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar
 9C:\Program Files\Java\jdk1.8.0_131\jre\classes
10
11Ext Class Loader加载路径:
12C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext
13C:\WINDOWS\Sun\Java\lib\ext
14
15App ClassLoader加载路径:
16C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar
17C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar
18C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar
19C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar
20C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar
21C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar
22C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar
23C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar
24C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar
25C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar
26C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar
27C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar
28C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar
29C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar
30C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar
31C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar
32C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar
33C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar
34C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar
35C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar
36C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar
37C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar
38C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
39E:\develop\workspace\IdeaProjects\JVMLearn\learn01\target\classes
40C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar

倒数第二行E:\develop\workspace\IdeaProjects\JVMLearn\learn01\target\classes这个路径是由IDE工具设置进去的。

双亲委托机制的优势

双亲委托机制的优势:

  1. 保证了java核心类库的类型安全

    java核心类库是由根类加载器加载的,如果没有双亲委托机制,那么用户可以自定义很多不同的类加载器去加载java的核心类库,由于不同类加载的器加载的类是不可见的,所以JVM中对于java.lang.String可能存在不同的版本,核心类库的类型兼容性就无法保证。

  2. 防止java核心类库被替换

    双亲委托机制确保了核心类库由根类加载器优先加载,防止被用户自定义类同名的类替换了。

  3. 不同的类加载器为相同名称的类创建了额外的命名空间

    相同名称的类可以并存在java虚拟机中,只需要用不同的类加载分别加载即可。不同的类加载器加载的同名的类是不兼容的,这就为java虚拟机创建了很多相互隔离的类空间,很多java框架使用了这个技术。