一:双亲委派机制
ClassLoader#loadClass(ClassLoader源码)
二:类加载器
普通的类只需要有1、2、3加载器就够了,有特殊需求需要4、5。
注:JVM调用BootstrapClassLoader启动,然后BootstrapClassLoader构造ExtClassLoader并启动它,加载扩展类,构造AppClassLoader,等到需要加载类路径下的某类到内存中才生效。即:JVM启动,BootstrapClassLoader和ExtClassLoader就会生效,AppClassLoader会等有类加载需求再生效,以上的加载都是自动加载,不需要程序员去调loadClass()进行加载。
1:BootStrapClassLoader
启动类加载器:加载的类库位置是"C:\Program Files\Java\jdk1.8.0_151\jre\lib"。
1.1:c++编写,已嵌入到了JVM内核当中。
1.2:如何通过Java的类加载器获取到,ExtClassLoader的getParent()获取到的是null(在类加载器部分:null就是指BootstrapClassLoader)。
/**
* 1:BootstrapClassLoader加载的类库
* System.getProperty("sun.boot.class.path”)
*
* 结果:
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\resources.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\rt.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\sunrsasign.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\jsse.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\jce.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\charsets.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\jfr.jar;
* C:\Program Files\Java\jdk1.8.0_151\jre\classes
*/
System.out.println(System.getProperty("sun.boot.class.path"));
2:ExtClassLoader
扩展类加载器:加载的类库位置是"C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext".
2.1:java编写,位于sun.misc包下,该包在你导入源代码的时候是没有的,需要重新去下,该包是openjdk的包,不公开源代码。
/**
* 1:ExtClassLoader加载的类库
* System.getProperty("java.ext.dirs")
*
* 结果:
* C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext;
* C:\Windows\Sun\Java\lib\ext
*/
System.out.println(System.getProperty("java.ext.dirs"));
3:AppClassLoader
应用程序类加载器:加载classpath下的class.
1:java编写,位于sun.misc包下,该包是openjdk的包,不公开源代码。
System.getProperty("java.class.path")的结果值就是AppClassLoader加载的类库。
比较好的理解就是:idea上自己编写的普通类就是AppClassLoader加载的。
4:CustomerClassLoader
自定义类加载器:自定义类加载器一般都是extends ClassLoader,然后重写其findClass()方法,不要去重写loadClass()方法,即不要去破坏其双亲委派的机制。
自定义类加载器的使用场景:
1:加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。
2:从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。
1、2的综合运用:比如你的应用需要通过网络来传输Java类的字节码,为了安全性,这些字节码经过了加密处理,这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码(findClass()),接着进行解密和验证,最后定义(defineClass())出在Java虚拟机中运行的类。
5:ThreadContextClassLoader
线程上下文类加载器(TCCL):每一个线程都有一个关联的ThreadContextClassLoader。
引入TCCL的原因:
Jvm的类加载机制是双亲委派机制,从父类加载器开始加载再子类加载器加载。若针对spi的接口和实现,spi的接口是核心类库,是由BootStrapClassLoader加载的,BootStrapClassLoader是加载不到第三方jar中的spi实现类的,因此引入线程上下文加载器,反向打通双亲委派,让BootStrapClassLoader可以指定线程上下文加载器来加载spi的实现类。
注:线程上下文加载器若不设置,则默认是AppClassLoader(该默认的类加载器是Jvm设置的)。
TCCL的作用:
1:解决委派双亲加载模式的缺点。
2:实现了jndi,spi接口的加载。
TCCL的使用场景:
1:当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。参考:[https://blog.csdn.net/yangcheng33/article/details/52631940](https://blog.csdn.net/yangcheng33/article/details/52631940)
2:当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。
Thread和TCCL的关系
new Thread()将继承父线程的TCCL。如果程序对TCCL没有任何改动,则程序的所有线程将都使用AppClassLoader作为TCCL。
设置TCCL的方式:Thread.currentThread().setContextClassLoader("类加载器")
注:TCCL是线程隔离的,每个线程可以有不同的TCCL。
个人认为,TCCL可以和自定义类加载器一起使用,设置某一类线程的类加载器为自定义类加载器,专门做一些处理。
/**
* <p>
* TCCL
* </p>
* @author: zhu.chen
* @date: 2020/8/3
* @version: v1.0.0
*/
public class Test {
public static void main(String[] args) {
/**
* 1:JVM会为每个线程设置默认TCCL为AppClassLoader
* 子线程t线程会继承main的TCCL也为AppClassLoader
*/
// main:sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(Thread.currentThread().getContextClassLoader());
Thread t = new Thread(() -> System.out.println("xxx"));
t.start();
// t:sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(t.getContextClassLoader());
/**
* 2:主线程设置了TCCL为Test1
* 子线程t1继承main的TCCL也为Test1(main和t的TCCL是一样的)
*/
Thread.currentThread().setContextClassLoader(new Test1());
// com.example.demo2.Test$Test1@79fc0f2f
System.out.println(Thread.currentThread().getContextClassLoader());
Thread t1 = new Thread(() -> System.out.println("xxx"));
t1.start();
// com.example.demo2.Test$Test1@79fc0f2f
System.out.println(t1.getContextClassLoader());
}
/**
* 自定义类加载器
*/
public static class Test1 extends ClassLoader {
}
}
三:Java虚拟机是如何判定两个Java类是相同的?
Java虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样,只有两者都相同的情况,才认为两个类是相同的,即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
网友评论