本文整理了基于 JDK8 的 ClassLoader 核心知识点,包括 JVM 中 ClassLoader 种类、ClassLoader 执行顺序、父加载器概念、双亲委派机制、自定义类加载器。
JDK 和 JRE 的作用
JAVAHOME、PATH、CLASSPATH
类加载器的种类
在JVM中有三类ClassLoader构成:
(1) Bootstrap ClassLoader
Bootstrap ClassLoader 最顶层的类加载器,主要加载核心类库 %JRE_HOME%\lib 下的 rt.jar、resources.jar、charsets.jar 和 class文件等。
- //执行
- System.out.println(System.getProperty("sun.boot.class.path"));
- //输出结果
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\sunrsasign.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;
- D:\Program Files\Java\jdk1.8.0_241\jre\classes
(2) Extention ClassLoader
Extention ClassLoader 扩展的类加载器,主要加载目录 %JRE_HOME%\lib\ext 目录下的jar包和class文件。
- //执行
- System.out.println(System.getProperty("java.ext.dirs"));
- //输出
- D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
(3) Appclass Loader
Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
类加载器的执行顺序
除启动类加载器(Bootstrap ClassLoader)外,扩展类加载器和应用类加载器都是通过类sun.misc.Launcher进行初始化,而Launcher类则由启动类加载器进行加载。Launcher相关代码如下:
- public Launcher() {
- Launcher.ExtClassLoader var1;
- try {
- //初始化扩展类加载器,构造函数没有入参,无法获取启动类加载器
- var1 = Launcher.ExtClassLoader.getExtClassLoader();
- } catch (IOException var10) {
- throw new InternalError("Could not create extension class loader", var10);
- }
- try {
- //初始化应用类加载器,入参为扩展类加载器
- this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
- } catch (IOException var9) {
- throw new InternalError("Could not create application class loader", var9);
- }
- // 设置上下文类加载器
- Thread.currentThread().setContextClassLoader(this.loader);
- //...
- }
加载顺序:Bootstrap CLassloder > Extention ClassLoader > AppClassLoader
父加载器概念
AppClassLoader 和 ExtClassLoader 都继承了 URLClassLoader。每个类加载器都有一个父加载器(注意:父类和父加载器是两个不同的概念),可通过 getParent() 获取父类加载器。
- System.out.println("ClassLoader is:"+cl.toString());
- System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
双亲委派
双亲委派模型:当一个类加载器接收到类加载请求时,会先请求其父类加载器加载,依次递归,当父类加载器无法找到该类时(根据类的全限定名称),子类加载器才会尝试去加载。
时序图
为什么使用双亲委派模型?
双亲委派模型是为了保证Java核心库的类型安全。所有Java应用都至少需要引用java.lang.Object类,在运行时这个类需要被加载到Java虚拟机中。如果该加载过程由自定义类加载器来完成,可能就会存在多个版本的java.lang.Object类,而且这些类之间是不兼容的。
通过双亲委派模型,对于Java核心库的类的加载工作由启动类加载器来统一完成,保证了Java应用所使用的都是同一个版本的Java核心库的类,是互相兼容的。
自定义类加载器
不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果我们需要动态加载比如从指定目录中加载一个class文件,这时候通过自定义类加载器可以实现。
自定义类加载器只需要继承java.lang.ClassLoader类,然后重写findClass(String name)方法即可,在方法中指明如何获取类的字节码流。如果要破坏双亲委派规范的话,还需重写loadClass方法(双亲委派的具体逻辑实现)。但不建议这么做。
- public class ClassLoaderTest extends ClassLoader {
- private String classPath;
- public ClassLoaderTest(String classPath) {
- this.classPath = classPath;
- }
- /**
- * 编写findClass方法的逻辑
- *
- * @param name
- * @return
- * @throws ClassNotFoundException
- */
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- // 获取类的class文件字节数组
- byte[] classData = getClassData(name);
- if (classData == null) {
- throw new ClassNotFoundException();
- } else {
- // 生成class对象
- return defineClass(name, classData, 0, classData.length);
- }
- }
- /**
- * 编写获取class文件并转换为字节码流的逻辑
- *
- * @param className
- * @return
- */
- private byte[] getClassData(String className) {
- // 读取类文件的字节
- String path = classNameToPath(className);
- try {
- InputStream is = new FileInputStream(path);
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- byte[] buffer = new byte[2048];
- int num = 0;
- // 读取类文件的字节码
- while ((num = is.read(buffer)) != -1) {
- stream.write(buffer, 0, num);
- }
- return stream.toByteArray();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 类文件的完全路径
- *
- * @param className
- * @return
- */
- private String classNameToPath(String className) {
- return classPath + File.separatorChar
- + className.replace('.', File.separatorChar) + ".class";
- }
- public static void main(String[] args) {
- String classPath = "/Users/zzs/my/article/projects/java-stream/src/main/java/";
- ClassLoaderTest loader = new ClassLoaderTest(classPath);
- try {
- //加载指定的class文件
- Class<?> object1 = loader.loadClass("com.secbro2.classload.SubClass");
- System.out.println(object1.newInstance().toString());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
1. 接口描述 接口请求域名: sms.tencentcloudapi.com 。 拉取单个号码短信回复...
预设策略配置 QcloudTCRFullAccess :容器镜像服务(TCR)全读写权限。 子账号绑...
在弹性伸缩添加和删除云服务器实例时,您要确保跨所有云服务器实例分配应用程序...
cc 域名 续费多少钱一年?cc域名是威瑞信注册局旗下运营的后缀,虽然是国别域名...
学校介绍 浙江大学是国家“211工程”和“985工程”重点建设的综合性大学,经过一...
使用集成了操作系统和应用程序的镜像,可以快速在ECS实例上部署Windows应用环境...
1. 接口描述 接口请求域名: gaap.tencentcloudapi.com 。 本接口(DescribeAcce...
描述 给定整数n和k,找到按字典序排序的第k个最小整数,范围从1到n。 1 ≤ k ≤ ...
2020年9月10日至11日,作为一年一度云计算领域的大型科技盛会,2020 AWS技术峰会...
qq 企业邮箱 免费版 域名 多少钱?虽然QQ企业 邮箱 提供免费版本,但免费并不是...