java类加载机制包含加载、验证、准备、解析、初始化、使用、卸载7个阶段,前5阶段构成类加载过程;其中解析可延迟至初始化后以支持动态绑定,各阶段常交叉执行而非严格串行。

类加载到底分哪几个阶段?
一个类从磁盘上的 .class 文件变成 JVM 中可执行的对象,完整生命周期是:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载。其中前五个阶段构成“类加载过程”,后两个是运行期行为。
注意:这七个阶段有固定触发顺序,但并非严格串行——比如解析可能延迟到初始化之后(为支持多态和动态绑定),而验证和准备常在加载过程中交叉进行。
每个阶段干了什么?重点在哪?
加载:找字节码、读进内存、建 Class 对象。
– 字节码来源灵活:本地文件、JAR/WAR 包、网络、动态生成(如代理)、甚至数据库或加密流;
– 关键产出是堆里的 java.lang.Class 实例,它是访问方法区元数据的唯一入口;
– 同一全限定名 + 同一类加载器 → 只有一个 Class 对象;不同加载器加载相同字节码 → 两个独立 Class 对象(equals 返回 false)。
验证:安全守门员,防止恶意或损坏字节码危害 JVM。
– 分四层:文件格式(魔数、版本号)、元数据(语法合法)、字节码(逻辑安全)、符号引用(链接可行);
– 虽耗时,但不可跳过;生产环境一般不关(-Xverifynone 仅用于极端性能调优)。
准备:给 static 变量分配内存(方法区),并设默认值(0 / false / null)。
– 不执行赋值语句:例如 static int x = 123; 此时 x 是 0,不是 123;
– 唯一例外:static final int y = 456;(编译期常量)→ 直接赋 456,跳过默认值阶段。
解析:把“名字”转成“地址”。
– 将常量池中的符号引用(如类名、方法名字符串)替换为直接引用(内存偏移或指针);
– 解析对象包括:类/接口、字段、静态/实例方法、接口方法等。
初始化:真正执行代码的阶段。
– 运行类构造器 <clinit>()</clinit> 方法(由编译器自动收集所有 static 变量赋值动作和 static 代码块组成);
– 触发条件明确:new 实例、调用静态方法、访问非 final 静态字段、反射、子类初始化、main 所在类启动;
– 父类优先初始化,且保证线程安全(JVM 自动加锁)。
谁来加载?双亲委派模型怎么运作?
JVM 内置三类加载器,形成树状委托链:
-
启动类加载器(Bootstrap):C++ 实现,加载
$JAVA_HOME/jre/lib下核心类(如 java.lang.Object),无 Java 对应类,getClassLoader()返回 null; -
扩展类加载器(Extension):Java 实现,父为 Bootstrap,加载
lib/ext或java.ext.dirs下扩展类; -
应用类加载器(Application):Java 实现,父为 Extension,加载 classpath 下用户类,也是
ClassLoader.getSystemClassLoader()返回的默认加载器。
双亲委派含义:当一个类加载器收到加载请求,先委托父加载器尝试,层层向上,直到 Bootstrap;只有父加载器无法完成(比如要加载的类不在其路径),才自己加载。好处是保障核心类不被篡改、避免类重复定义。
什么时候需要打破双亲委派?怎么破?
标准模型很安全,但现实场景常需绕过:
- 热部署/模块隔离:如 Tomcat 为每个 Web 应用创建独立加载器,避免共享类冲突;
- OSGi 动态插件:模块间精确控制类可见性,必须绕过统一委派;
- 自定义类来源:从数据库、加密流、远程服务加载类,父加载器根本找不到字节码。
打破方式很简单:重写自定义 ClassLoader.loadClass() 方法,去掉 super.loadClass() 调用,改为先自己尝试加载(如 findClass()),失败再委托父类——即“先自后父”,而非“先父后自”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











