一个对象从出生到销毁在JVM中都经历了什么?
JVM
1. 类加载与准备
类加载(Loading) 当我们在代码中第一次使用某个类(如
new
、访问静态字段等)时,Java 虚拟机(JVM)的类加载子系统会尝试去加载这个类的.class
文件。这一步包括从各种来源(本地文件系统、网络、JAR 包等等)读取
.class
字节码,并将其加载到内存中,形成Class
对象。
链接(Linking) 类在加载到内存后,JVM 会对其进行链接操作,主要包括:
验证(Verification):确保字节码符合 JVM 规范,不会破坏虚拟机安全性。
准备(Preparation):为静态变量分配内存并设置默认初始值。
解析(Resolution):将常量池中的符号引用解析为直接引用(比如将方法、字段等符号引用解析到具体的内存地址)。
初始化(Initialization) 链接结束后,就开始执行类初始化逻辑(即执行类中的
<clinit>
方法),对静态变量进行真正的初始化(如果有赋值或静态代码块)。直到这一步完成后,类才算是真正可以被使用。
2. 对象创建
当类已经被正确加载并完成初始化后,才可以实例化对象。一个典型的对象创建过程可分为以下步骤:
分配内存
JVM 会通过内存分配策略(如指针碰撞、空闲列表等)在 Java 堆上为新对象分配一块连续的内存空间。
同时,如果开启了“逃逸分析”以及“栈上分配”等高级优化,可能会将对象优化分配到栈上或进行标量替换(对开发者是透明的)。
初始化对象头
包括对象自身的 Mark Word(存放哈希码、GC 信息、锁标记等)和 类型指针(指向对象所属的类的元数据信息),以及如果是数组还会多一个用于记录数组长度的空间。
执行构造方法
JVM 调用相应的构造方法(
<init>
),对对象进行初始化,比如给实例变量赋初始值等。只有构造方法执行完毕,才算真正生成了一个完整可用的对象。
返回对象引用
在 Java 中,对象在堆上分配后,JVM 会将对象的引用(在大多数实现中是一种句柄或直接指针)返回给调用方,将其保存在相应的栈帧变量或字段等位置。
3. 对象的使用
在对象存活期间,程序会对其进行各种操作:
方法调用:访问对象实例方法、获取或设置实例变量等。
方法栈帧与引用传递:对象引用会随着方法调用在栈帧中被不断复制或传递。
逃逸分析:JIT 编译器可能会在运行时判断对象引用的作用范围,做一些优化(如栈上分配或消除同步锁)。
只要堆中的对象还可以被 GC Roots(一般是栈中的引用、方法区的静态引用、JNI 引用等)直接或间接引用到,就被称为“可达”对象。可达对象会被保留,不会被垃圾回收。
4. 垃圾回收前的可达性分析
当 JVM 判断需要进行垃圾回收(GC)时,通常会进行一次可达性分析(Reachability Analysis),其流程大致如下:
可达性分析(Root Searching)
先从一组称为 GC Roots 的引用(如虚拟机栈、常量引用、类静态引用、JNI 引用等)开始搜索,遍历所有能够触及到的对象。
能被 GC Roots 触及的对象都标记为“存活对象”,搜索结束后仍未被标记的对象即“不可达对象”。
垃圾收集算法
常见有标记-清除(Mark-Sweep)、复制(Copying)、标记-整理(Mark-Compact)、分代收集(Generational Collecting)等各种实现。HotSpot VM 大多数情况使用分代收集的策略:年轻代(Minor GC)+ 老年代(Major GC / Full GC)配合工作。
如果对象在可达性分析中被判断为不可达,则会进入下一步的垃圾回收处理阶段。
5. 垃圾回收及对象销毁
Finalization(终结)阶段
在 Java 早期版本中,存在
finalize()
方法用于对象即将被回收时做一些清理操作;但是在现代 Java 中已经不鼓励使用它,并且在 Java 9 及后续版本中更是被标记为“废弃”,原因是finalize()
机制存在不可预测性、低性能和可能带来危险行为等问题。一般情况下,开发者应该使用 try-with-resources、AutoCloseable 或者其他显式资源管理方式来进行对象资源的清理和释放,而不是依赖
finalize()
。
回收内存
GC 线程对不可达对象对应的内存进行回收,释放其占用的堆空间。
常见算法包括标记-清除、标记-整理、复制算法等,具体使用哪种算法取决于垃圾收集器类型(Serial、Parallel、CMS、G1、ZGC、Shenandoah 等)。
对象真正被销毁
当 GC 完成后,那些不可达对象所占用的内存就被释放,意味着这些对象在堆内存层面彻底消失,其生命周期也就正式结束。
对应的引用(如果还存在的话)只是失去指向的目标,不再代表任何有效的对象。
6. 总结
类加载阶段:JVM 把类的字节码加载到方法区(或对应的元空间),并完成验证、准备、解析、初始化,使类处于可用状态。
对象创建阶段:使用
new
等方式触发分配内存、初始化对象头和执行构造函数,并返回对象引用。对象使用阶段:对象在堆中存活,只要还有活动引用,JVM 就不会回收它;使用过程中可能触发 JIT 编译、逃逸分析等优化。
可达性分析(垃圾回收判断):当 GC 触发时,使用可达性分析判断对象是否仍然与 GC Roots 可达。
垃圾回收与销毁:被判断为不可达的对象会被垃圾收集器回收,其占用的堆内存被释放;如果类中实现了
finalize()
,在老版本中会在回收前被调用,但现代 Java 中已不鼓励使用。
整体上,一个 Java 对象的生命周期基本就包含了**诞生(对象的分配、初始化)→ 存活(被引用、被使用)→ 回收(不可达、被 GC 回收)**这么一个过程。通过垃圾回收的机制,JVM 能够自动地管理对象内存,减少内存管理的复杂度,同时保证安全性和稳定性。
Last updated
Was this helpful?