github地址:
https://github.com/lishanglei/jvm_study.git
类加载器的命名空间:
每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父类加载器所加载的类组成
在同一个命名空间中,不会出现类的全限定名完全相同的两个类
在不同的命名空间中,有可能会出现类的全限定名完全相同的两个类
关于命名空间的重要说明:
- 子加载器所加载的类能够访问到父加载器所加载的类
- 父加载器所加载的类无法访问子加载器所加载的类
public class MyCat {
public MyCat() {
System.out.println("MyCat is loaded by :" +this.getClass().getClassLoader());
}
}
public class MySample {
public MySample() {
System.out.println("MySample is loaded by :" +this.getClass().getClassLoader());
//实例化MyCat对象前会先加载MyCat,会使用加载MySample类的类加载器进行加载
new MyCat();
}
}
public class MyTest17_1 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyTest16 loader1 =new MyTest16("loader1");
loader1.setPath("C:\\Users\\10025\\Desktop\\");
Class<?> clazz = loader1.loadClass("com.jvm.class_loader.MySample");
System.out.println("class: "+clazz.hashCode());
//该行会实例化MySample对象
Object object =clazz.newInstance();
}
}
操作步骤:
编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。
1.删除项目中MySample.class文件,保留MyCat.class文件
运行结果:
findClass invoked
loadClassData invoked
class: 2133927002
MySample is loaded by :[ loader1 ]
MyCat is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2
结果分析:
MySample由loader1加载器进行加载,根据双亲委托模型,类加载器顺序:loader1->System->Extension->Bootstrap->Extension->System->loader1
MyCat由System系统类加载器进行加载,根据双亲委托模型,类加载器顺序:loader1->System->Extension->Bootstrap->Extension->System
2.重新编译,删除项目中MyCat.class文件,保留MySample.class文件
运行结果:
class: 1735600054
MySample is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread “main” java.lang.NoClassDefFoundError: com/jvm/class_loader/MyCat
at com.jvm.class_loader.MySample.(MySample.java:18)
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 com.jvm.class_loader.MyTest17_1.main(MyTest17_1.java:22)
Caused by: java.lang.ClassNotFoundException: com.jvm.class_loader.MyCat
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
… 7 more
结果分析:
MySample由System系统加载器进行加载,根据双亲委托模型,类加载器顺序:loader1->System->Extension->Bootstrap->Extension->System
而MyCat在加载时会默认使用加载MySample的类加载器进行加载,根据双亲委托模型,类加载器顺序:System->Extension->Bootstrap->Extension->System,结果没有类加载器可以加载MyCat类
public class MyCat {
public MyCat() {
System.out.println("MyCat is loaded by :" +this.getClass().getClassLoader());
System.out.println("from MyCat: "+MySample.class);
}
}
还是上述代码,在MyCat增加一行代码 System.out.println("from MyCat: "+MySample.class);
操作步骤:
编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。
1.删除项目中MySample.class文件,保留MyCat.class文件
运行结果:
findClass invoked
loadClassData invoked
class: 2133927002
MySample is loaded by :[ loader1 ]
MyCat is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread “main” java.lang.NoClassDefFoundError: com/jvm/class_loader/MySample
at com.jvm.class_loader.MyCat.(MyCat.java:17)
at com.jvm.class_loader.MySample.(MySample.java:18)
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 com.jvm.class_loader.MyTest17_1.main(MyTest17_1.java:22)
Caused by: java.lang.ClassNotFoundException: com.jvm.class_loader.MySample
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
… 8 more
结果分析:
MySample 由自定义类加载loader1加载,而MyCat由系统类加载器加载,不同的命名空间,导致MyCat在调用MySample.class属性时在系统类加载器的命名空间中找不到MySample的Class对象.即父加载器不能调用其子加载器命名空间中所加载的Class对象
public class MyCat {
public MyCat() {
System.out.println("MyCat is loaded by :" +this.getClass().getClassLoader());
//System.out.println("from MyCat: "+MySample.class);
}
}
public class MySample {
public MySample() {
System.out.println("MySample is loaded by :" +this.getClass().getClassLoader());
//实例化MyCat对象前会先加载MyCat,会使用加载MySample类的类加载器进行加载
new MyCat();
System.out.println("from MySample :" +MyCat.class);
}
}
注释掉MyCat中刚刚添加的那一行代码,在MySample中添加一行代码System.out.println(“from MySample :” +MyCat.class);
操作步骤:
编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。
1.删除项目中MySample.class文件,保留MyCat.class文件
**运行结果:**findClass invoked
loadClassData invoked
class: 2133927002
MySample is loaded by :[ loader1 ]
MyCat is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2
from MySample :class com.jvm.class_loader.MyCat
结果分析:
MySample由自定义类加载器loader1加载,而MyCat由系统类加载器加载.由于系统类加载器是自定义加载器的父加载器,在自定义加载器中是可以访问由父加载器加载的Class对象
public class MyPerson {
private MyPerson myPerson;
public void setMyPerson(Object myPerson) {
this.myPerson =(MyPerson) myPerson;
}
}
public class MyTest21 {
public static void main(String[] args) throws Exception {
MyTest16 loader1 =new MyTest16("loader1");
MyTest16 loader2 =new MyTest16("loader2");
loader1.setPath("C:\\Users\\10025\\Desktop\\");
loader2.setPath("C:\\Users\\10025\\Desktop\\");
Class<?> clazz1 = loader1.loadClass("com.jvm.class_loader.MyPerson");
Class<?> clazz2 = loader2.loadClass("com.jvm.class_loader.MyPerson");
System.out.println(clazz1==clazz2);
Object object1 =clazz1.newInstance();
Object object2 =clazz2.newInstance();
Method method =clazz1.getMethod("setMyPerson", Object.class);
method.invoke(object1, object2);
}
}
操作步骤:
编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。
1.删除项目中MyPerson.class文件
输出结果:
类加载器: loader1
类加载器: loader2
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 com.jvm.class_loader.MyTest21.main(MyTest21.java:33)
Caused by: java.lang.ClassCastException: com.jvm.class_loader.MyPerson cannot be cast to com.jvm.class_loader.MyPerson
at com.jvm.class_loader.MyPerson.setMyPerson(MyPerson.java:17)
… 5 more
结果分析:
由于项目中生成的MyPerson.class文件被删除,所以类加载loader1,loader2分别加载了MyPerson类到各自的命名空间中,且互相不可见.因此System.out.println(clazz1==clazz2);输出为false.
综上所述得出如下结论:
- 同一个命名空间内的类是相互可见的.
- 子加载器的命名空间包含所有父加载器的命名空间.因此由子加载器加载的类能看见父加载器加载的类.例如系统类加载器加载的类可以看见根类加载器加载的类
- 由父类加载器加载的类不能看见子加载器加载的类.
- 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见
双亲委托模型的优点:
- 确保java核心库的类型安全,所有的java引用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被加载到虚拟机中;如果这个加载过程是由Java引用自己的类加载器所完成的,那么很可能会在JVM中存在多个版本的java.lang.Object类,而且这些类之间还是不兼容的,互相不可见的(正式命名空间在发挥着作用).借助于双亲委托机制,java核心类库中的类加载工作都是由启动类加载器来完成,从而确保了java应用所使用的都是同一个版本的java核心类库,它们之间相互兼容.
- 可以确保java核心类库所提供的类不会被自定义的类所替代
- 不同的类加载器可以为相同全限定名的类创建额外的命名空间,相同限定名的类可以并存在java虚拟机中,只需要不同的类加载器来加载它们即可.不同的类加载器所加载的类之间是不兼容,不可见的,这就相当于在java虚拟机内部创建了一个又一个相互隔离的java类空间,这类技术在很多框架中得到了实际应用.