jvm原理(16)类加载器实战剖析与疑难点解析

三大类加载器所加载的路径范围:

1
2
3
4
5
6
7
public class MyTest18 {
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));//系统类加载器加载路径
System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器加载路径
System.out.println(System.getProperty("java.class.path"));//应用类加载器加载过程
}
}

结果(根据自身环境不同会有所差异):

1
2
3
4
5
6
C:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_111\jre\classes
-------------------------------------------
C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
-------------------------------------------
C:\Program Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;E:\Study\intelIde\jvm_lecture\out\production\classes;D:\IntelliJ IDEA 2017.2.4\lib\idea_rt.jar

下面我写一个程序:

1
2
3
4
5
6
7
8
9
10
public class MyTest18_1 {
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
loader1.setPath("E:\\data\\classes\\");
Class<?> clazz = loader1.loadClass("com.twodragonlake.jvm.classloader.MyTest");
System.out.println("class : "+clazz.hashCode());
System.out.println("class loader :" + clazz.getClassLoader());
}
}

打印结果:

1
2
class : 1735600054
class loader :sun.misc.Launcher$AppClassLoader@18b4aac2

MyTest是由应用类加载器加载,这个没啥毛病,那么我们能不能想办法让启动类加载器去加载MyTest呢?要想让启动类加载器加载MyTest必须让启动类加载器能够找到MyTest的class文件,那就需要把MyTest放到启动类加载器的加载目录里边。ok,那么我们找到启动类加载器的一个路径比如【C:\Program Files\Java\jdk1.8.0_111\jre\classes】我们定位到此目录下边,此时你会发现C:\Program Files\Java\jdk1.8.0_111\jre\下边并没有classes文件夹,我们新建一个classes即可,既然sun.boot.class.path变量指定了这个目录我们就能从里边尝试加载类,然后我们把工程下边的【com.twodragonlake.jvm.classloader.MyTest.class】放到C:\Program Files\Java\jdk1.8.0_111\jre\下边,
这里写图片描述
OK,此时我们运行MyTest18_1 得到打印结果:

1
2
class : 2133927002
class loader :null

null表明是由启动类加载器加载。即,MyTest.class是由启动类加载器加载。做完实验时候可以把classes目录删除,以免造成混绕。

接下来看一下扩展类加载器:

1
2
3
4
5
6
7
8
9

public class MyTest19 {
public static void main(String[] args) {
AESKeyGenerator aesKeyGenerator = new AESKeyGenerator();
System.out.println(aesKeyGenerator.getClass().getClassLoader());
System.out.println(MyTest19.class.getClassLoader());
}
}

打印结果:

1
2
sun.misc.Launcher$ExtClassLoader@12a3a380
sun.misc.Launcher$AppClassLoader@18b4aac2

那好我们能不能修改扩展类加载器的加载路径java.ext.dirs的值,让其从当前目录加载如何?
这里写图片描述
显然AESKeyGenerator无法被加载,因为当前目录不存在这个类。

下一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyTest20 {
public static void main(String[] args) throws Exception{
MyTest16 loader1 = new MyTest16("loader1") ;
MyTest16 loader2 = new MyTest16("loader2") ;
Class<?> clazz1 = loader1.loadClass("com.twodragonlake.jvm.classloader.MyPerson");
Class<?> clazz2 = loader2.loadClass("com.twodragonlake.jvm.classloader.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);
}
}

打印结果为true,原因是虽然loader1和loader2是2个不同的类加载器实例,但是他们都会委托应用类加载器去加载,如果应用类加载器加载过MyPerson,第二次不会再次加载。所以这2个加载器加载的类返回的结果是同一个类。
下边的我们用clazz1创建的Method,然后调用的时候我们传入的是clazz2的对象object2,由于object1和object2的class是同一个class,所以向下类型转换不会出现问题。