父子类加载器的命名空间
类A中引用了类B,那么加载类B时,由类A的类加载器按照双亲委托机制尝试加载。子类加载器加载的类可以引用父类加载器加载的类,而父类加载器加载的类不能引用子类加载器加载的类。
测试案例如下:
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工具设置进去的。
双亲委托机制的优势
双亲委托机制的优势:
-
保证了java核心类库的类型安全
java核心类库是由根类加载器加载的,如果没有双亲委托机制,那么用户可以自定义很多不同的类加载器去加载java的核心类库,由于不同类加载的器加载的类是不可见的,所以JVM中对于java.lang.String可能存在不同的版本,核心类库的类型兼容性就无法保证。
-
防止java核心类库被替换
双亲委托机制确保了核心类库由根类加载器优先加载,防止被用户自定义类同名的类替换了。
-
不同的类加载器为相同名称的类创建了额外的命名空间
相同名称的类可以并存在java虚拟机中,只需要用不同的类加载分别加载即可。不同的类加载器加载的同名的类是不兼容的,这就为java虚拟机创建了很多相互隔离的类空间,很多java框架使用了这个技术。