JVM的类加载机制

JVM的类加载机制是Java"一次编写,到处运行"和动态性的基石。它的核心任务就是:找到并验证字节码文件(.class),将其定义成JVM能直接使用的Java类

简单来说,这个过程由三大部分组成:加载、连接、初始化。连接又可以细分为验证、准备、解析。


一、类加载器的体系结构(双亲委派模型)

这是JVM类加载机制最核心的设计。JVM不会用一个"万能加载器",而是使用一组有层次关系的加载器。

bash 复制代码
                 启动类加载器 (Bootstrap ClassLoader)
                    ↑  (父加载器,实际是C++实现,Java中为null)
                 扩展类加载器 (Extension ClassLoader)
                    ↑  (父加载器为 Bootstrap)
                 应用程序类加载器 (Application ClassLoader)
                    ↑  (父加载器为 Extension)
                 自定义类加载器 (Custom ClassLoader)
  • 启动类加载器 :加载 JAVA_HOME/lib 目录下的核心类库,比如 rt.jar 中的 java.lang.*。它是最顶层的,没有父加载器。

  • 扩展类加载器 :加载 JAVA_HOME/lib/ext 目录下的扩展类库。

  • 应用程序类加载器 :加载用户类路径(CLASSPATH)下的类,也就是你编写的 .class 文件。

  • 自定义类加载器 :开发者可以继承 ClassLoader 类,实现自定义的加载逻辑,比如从网络、数据库或加密文件中加载类。

双亲委派模型的工作流程

当一个类加载器(比如应用程序类加载器)收到加载一个类的请求时,它不会自己立即去加载,而是:

  1. 向上委派:先将请求委派给它的父加载器(扩展类加载器)。

  2. 递归检查:父加载器同样会继续向上委派给自己的父加载器(启动类加载器),直到最顶层的启动类加载器。

  3. 尝试加载:每个父加载器尝试在自己的加载路径中查找并加载这个类。

  4. 向下查找:如果所有父加载器都无法加载,才会回到最初发起请求的子加载器,让它自己去类路径里查找并加载。

这样做的好处

  • 避免重复加载:父加载器加载过的类,子加载器不会再次加载。

  • 保证核心类库的安全 :你无法自己写一个 java.lang.Object 类去替换系统的。因为当请求加载 java.lang.Object 时,双亲委派会一直向上委派给启动类加载器,它找到并加载了正确的 Object 后,应用程序类加载器就不会再加载你写的"假"类了。


二、类的生命周期:加载、连接、初始化

一个类从被加载到内存,到卸载出内存,完整的生命周期包括7个阶段。其中,加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的。

1. 加载(Loading)

JVM要做三件事:

  • 通过一个类的全限定名 (如 java.lang.String)获取其二进制字节流(来源可以是 .class 文件、jar包、网络、甚至动态生成)。

  • 将这个字节流所代表的静态存储结构转化为方法区(元空间)的运行时数据结构

  • 在内存的堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类各种数据的访问入口。

加载是类加载器工作的主要阶段,并且可以结合"双亲委派模型"来理解。

2. 连接(Linking)

分为三步,其中解析阶段有时可能在初始化之后才开始。

  • 验证(Verification) :确保 .class 字节流符合JVM规范,不会危害JVM安全。检查项很多:文件格式、元数据、字节码、符号引用等。这是类加载机制安全防护的第一关。

  • 准备(Preparation) :为类变量(static 修饰的变量)在方法区中分配内存 ,并设置默认初始值(零值)。

    • public static int value = 123;:在准备阶段后,value 的值是 0,而不是 123123 是在后面的初始化阶段才赋值的。

    • 如果变量是 static final(常量),情况不同。例如 public static final int value = 123;,编译器会在准备阶段就直接给 value 赋值为 123

  • 解析(Resolution) :将常量池内的符号引用 替换为直接引用

    • 符号引用 :字面上的引用,例如一个类中调用了另一个类的 method(),在字节码里就是 "com/example/OtherClass.method" 这样一串文字。

    • 直接引用:指向目标的内存地址,或者相对于方法区某个位置的偏移量。一旦找到具体的内存地址,后面的调用就是真正的内存访问了。

3. 初始化(Initialization)

这是类加载过程的最后一步 。JVM会执行类构造器 <clinit>() 方法,为类变量赋程序员定义的初始值,并执行静态代码块。

  • <clinit>() 方法由编译器自动收集类中所有类变量的赋值动作静态语句块中的语句合并产生。

  • 对于 public static int value = 123; :在初始化阶段,value 才会真正被赋值为 123

JVM 保证在首次主动使用一个类时才会初始化它。主动使用的情况包括:

  • new 一个对象

  • 访问或调用一个类的静态方法/静态字段(final 常量除外)

  • 反射调用

  • 初始化一个类时,其父类尚未初始化,则先初始化父类

  • 主类(包含 main 方法的类)


一个完整的例子

假设运行 new MyApp()

  1. 加载 :应用程序类加载器收到请求,向上委派,最终由应用程序类加载器自己找到 MyApp.class,加载成 Class 对象。

  2. 连接

    • 验证 :检查 MyApp.class 格式是否正确。

    • 准备 :为 MyApp 的静态变量分配内存并设零值(比如 static int count = 10; 此时 count=0)。

    • 解析 :将 MyApp 内部引用的 System.out 等符号,解析成真正的内存地址。

  3. 初始化 :执行静态代码块和静态变量赋值,count 被赋值为 10。如果父类未初始化,先递归初始化父类。

  4. 完成!现在可以安全地创建 MyApp 实例了。

理解类加载机制,是排查ClassNotFoundExceptionNoClassDefFoundError ,以及实现热部署、字节码增强等技术的基础。如果你还想继续了解类的卸载 条件,或者自定义类加载器如何实现,我可以进一步为你讲解。

相关推荐
fengxin_rou2 小时前
Java垃圾回收机制深度解析:从原理到实战
java·jvm·性能优化·gc·垃圾回收
cfm_29142 小时前
Java JVM 零基础入门
java·jvm
wuminyu4 小时前
Java锁机制之park与futex系统级协同机制解析
java·linux·c语言·jvm·c++
海兰11 小时前
【水浒传:第二篇】AI江湖 —项目详细设计指南(一)
jvm·人工智能·游戏
J-Tony1116 小时前
【JVM】三色标记法
java·jvm·算法
wuminyu17 小时前
Java锁机制之park和unpark源码剖析
java·linux·c语言·jvm·c++
一只小白0001 天前
【JVM | 第五篇】—— 深入理解垃圾回收
jvm·测试工具
IT龟苓膏1 天前
Java 集合进阶:ConcurrentHashMap、HashSet、LinkedHashMap、TreeMap 和 fail-fast 一篇讲清
java·开发语言·jvm
J-Tony111 天前
【JVM】双亲委派
jvm