【JVM】之 类加载(ClassLoader)

本文详细介绍了Java类加载过程中的加载、链接与初始化步骤,并深入探讨了双亲委派模型的工作原理及其优缺点。此外,还讲解了如何通过自定义类加载器突破传统加载模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在冯.诺依曼定义的计算机模型中,任何程序都需要加载到内存才能与 CPU 进行交流

字节码 .class 文件同样需要加载到内存中,才可以实例化类。

在加载类时, 使用的是 Parents Delegation Model ,译为 双亲委派模型



一、类加载器作用


Java 的类加载器是一个运行时核心基础设施模块。

主要是在启动之初进行类的 加载(Load)、链接(Link)、初始化(Init)

具体如图:

在这里插入图片描述


(1)加载 Load

Load 阶段读取类文件产生二进制流, 并转化为特定的数据结构,初步校验 cafe babe 魔法数、常量池、文件长度、是否有父类等, 然后创建对应类的 java.Jang.Class 实例

在这里插入图片描述


(2)链接 Link

Link 阶段包括验证、准备、解析三个步骤。

  1. 验证是更详细的校验,比如 final 是否合规、类型是否正确、静态变量是否合理等

  2. 准备阶段是为静态变量分配内存,并设定默认值。

  3. 解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局。


(3)初始化 Init

Init 阶段执行类构造器<clinit> 方法,如果赋值运算是通过其他类的静态方法来完成的, 那么会马上解析另外个类,在虚拟机枪中执行完毕后通过返回值进行赋值。



二、类加载详情


类加载是一个将 .class 字节码文件实例化成 Class 对象并进行相关初始化的过程。

在这个过程中, JVM 会初始化继承树上还没有被初始化过的所有父类,并且会执行这个链路上所有未执行过的静态代码块、静态变量赋值语旬等。


(1)双委托模型

如图:

在这里插入图片描述

作用:为了避免重复加载,由下到上逐级委托,由上到下逐级查找

  1. 首先不会自己去加载类,而是把这个请求委托给父加载器去完成
  2. 只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。

在这里插入图片描述


1)启动类加载器

Bootstarp ClassLoader

负责加载机器上安装的Java目录下的核心类

JVM启动后,首先依托启动类加载器,去加载环境下lib目录中的核心类库


2)扩展类加载器

Extension ClassLoader 加载 lib/ext目录下


3)应用程序类加载器

Application ClassLoader 去加载ClassPath环境变量所指定的路径中的类

其实,就是去加载自己写的Java代码,这个类加载负载将那些类加载到内存中。


4)自定义类加载器

自定义类加载器,根据需求去加载你的类。

在这里插入图片描述

在这里插入图片描述


(2)举个栗子


public class ClassLoaderDemo { 

  

    public static void main(String[] args) throws ClassNotFoundException { 

  

        // 加载核心类库的 Bootstrap ClassLoader 

        System.out.println("核心类库加载器: " + ClassLoaderDemo.class.getClassLoader() 

                .loadClass("java.lang.String").getClassLoader()); 

  

        // 加载拓展库的 Extension ClassLoader 

        System.out.println("扩展类库加载器: " + ClassLoaderDemo.class.getClassLoader() 

                .loadClass("com.sun.nio.zipfs.ZipCoder").getClassLoader()); 

  

        // 加载应用程序 

        System.out.println("应用程序库加载器: " + ClassLoaderDemo.class.getClassLoader()); 

  

        // 双亲委派模型 Parents Delegation Model 

        System.out.println("应用程序库加载器的父类: " + ClassLoaderDemo.class.getClassLoader().getParent()); 

        System.out.println("应用程序库加载器的父类的父类: " + ClassLoaderDemo.class.getClassLoader().getParent().getParent()); 

    } 

} 

输出


核心类库加载器: null 

扩展类库加载器: sun.misc.Launcher$ExtClassLoader@63947c6b 

应用程序库加载器: sun.misc.Launcher$AppClassLoader@18b4aac2 

应用程序库加载器的父类: sun.misc.Launcher$ExtClassLoader@63947c6b 

应用程序库加载器的父类的父类: null 



三、类卸载


类什么时候会被卸载?

满足以下两个条件:

  1. Class所有的实例都已经被GC
  2. 加载该类的ClassLoader实例已经被GC

可以通过jvm -verbose:class 输出类加载和卸载的日志信息



四、类热加载


ClassLoaderClass 确定一个 类

在这里插入图片描述

.java重新编译为.class文件

通过创建新的类加载器来实现热加载。


URL classURL = new URL("file:/"); 

  

URLClassLoader loader = new URLClassLoader(new URL[] {classURL}); 

  

Class clazz = loader.loadClass("HelloService"); 



五、问题


(1)如何突破双亲模式?

可以通过重载 ClassLoader 来修改双亲委托模式。

即,重写loadClass方法,来改变类的加载次序。
比如:先使用自定义类加载器加载,如果加载不到,则交给双亲加载。

实现方案:

  1. 使用线程上下文类加载器实现,让父类加载器请求子类加载器去完成类加载的动作。

Thread.currentThread().setContextClassLoader(loader); 

  1. 直接创建,然后去加载

// 注意 parent 

URLClassLoader loader = new URLClassLoader(new URL[] {classURL}); 


(2)JVM 如何知道类在哪?

可以查看openjdk源码:sun.misc.Launcher.AppClassLoader

即,读取java.class.path配置,指定去哪些地址加载类资源。

验证下:

  1. jps查看本机 Java 进程

  2. 查看运行时配置:jcmd 进程号 VM.system_properties

Java里天生可以动态扩展的语言特性就是依赖运行期动态加载动态连接。(Tips:例如AOP(动态代理),因为Java是静态,不像Ruby Python 运行时修改源码,Java不行,Java只能修改字节码来实现运行时动态。即根据Class,读取字节码,进行修改,再形成字节数组,写入内存或文件,从而实现。)

其他资料:Javasisit,ASM。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值