一个对象从出生到销毁在JVM中都经历了什么?

JVM

1. 类加载与准备

  1. 类加载(Loading) 当我们在代码中第一次使用某个类(如 new、访问静态字段等)时,Java 虚拟机(JVM)的类加载子系统会尝试去加载这个类的 .class 文件。

    • 这一步包括从各种来源(本地文件系统、网络、JAR 包等等)读取 .class 字节码,并将其加载到内存中,形成 Class 对象。

  2. 链接(Linking) 类在加载到内存后,JVM 会对其进行链接操作,主要包括:

    • 验证(Verification):确保字节码符合 JVM 规范,不会破坏虚拟机安全性。

    • 准备(Preparation):为静态变量分配内存并设置默认初始值。

    • 解析(Resolution):将常量池中的符号引用解析为直接引用(比如将方法、字段等符号引用解析到具体的内存地址)。

  3. 初始化(Initialization) 链接结束后,就开始执行类初始化逻辑(即执行类中的 <clinit> 方法),对静态变量进行真正的初始化(如果有赋值或静态代码块)。直到这一步完成后,类才算是真正可以被使用。


2. 对象创建

当类已经被正确加载并完成初始化后,才可以实例化对象。一个典型的对象创建过程可分为以下步骤:

  1. 分配内存

    • JVM 会通过内存分配策略(如指针碰撞、空闲列表等)在 Java 堆上为新对象分配一块连续的内存空间。

    • 同时,如果开启了“逃逸分析”以及“栈上分配”等高级优化,可能会将对象优化分配到栈上或进行标量替换(对开发者是透明的)。

  2. 初始化对象头

    • 包括对象自身的 Mark Word(存放哈希码、GC 信息、锁标记等)和 类型指针(指向对象所属的类的元数据信息),以及如果是数组还会多一个用于记录数组长度的空间。

  3. 执行构造方法

    • JVM 调用相应的构造方法(<init>),对对象进行初始化,比如给实例变量赋初始值等。

    • 只有构造方法执行完毕,才算真正生成了一个完整可用的对象。

  4. 返回对象引用

    • 在 Java 中,对象在堆上分配后,JVM 会将对象的引用(在大多数实现中是一种句柄或直接指针)返回给调用方,将其保存在相应的栈帧变量或字段等位置。


3. 对象的使用

在对象存活期间,程序会对其进行各种操作:

  1. 方法调用:访问对象实例方法、获取或设置实例变量等。

  2. 方法栈帧与引用传递:对象引用会随着方法调用在栈帧中被不断复制或传递。

  3. 逃逸分析:JIT 编译器可能会在运行时判断对象引用的作用范围,做一些优化(如栈上分配或消除同步锁)。

只要堆中的对象还可以被 GC Roots(一般是栈中的引用、方法区的静态引用、JNI 引用等)直接或间接引用到,就被称为“可达”对象。可达对象会被保留,不会被垃圾回收。


4. 垃圾回收前的可达性分析

当 JVM 判断需要进行垃圾回收(GC)时,通常会进行一次可达性分析(Reachability Analysis),其流程大致如下:

  1. 可达性分析(Root Searching)

    • 先从一组称为 GC Roots 的引用(如虚拟机栈、常量引用、类静态引用、JNI 引用等)开始搜索,遍历所有能够触及到的对象。

    • 能被 GC Roots 触及的对象都标记为“存活对象”,搜索结束后仍未被标记的对象即“不可达对象”。

  2. 垃圾收集算法

    • 常见有标记-清除(Mark-Sweep)复制(Copying)标记-整理(Mark-Compact)分代收集(Generational Collecting)等各种实现。HotSpot VM 大多数情况使用分代收集的策略:年轻代(Minor GC)+ 老年代(Major GC / Full GC)配合工作。

如果对象在可达性分析中被判断为不可达,则会进入下一步的垃圾回收处理阶段


5. 垃圾回收及对象销毁

  1. Finalization(终结)阶段

    • 在 Java 早期版本中,存在 finalize() 方法用于对象即将被回收时做一些清理操作;但是在现代 Java 中已经不鼓励使用它,并且在 Java 9 及后续版本中更是被标记为“废弃”,原因是 finalize() 机制存在不可预测性、低性能和可能带来危险行为等问题。

    • 一般情况下,开发者应该使用 try-with-resourcesAutoCloseable 或者其他显式资源管理方式来进行对象资源的清理和释放,而不是依赖 finalize()

  2. 回收内存

    • GC 线程对不可达对象对应的内存进行回收,释放其占用的堆空间。

    • 常见算法包括标记-清除、标记-整理、复制算法等,具体使用哪种算法取决于垃圾收集器类型(Serial、Parallel、CMS、G1、ZGC、Shenandoah 等)。

  3. 对象真正被销毁

    • 当 GC 完成后,那些不可达对象所占用的内存就被释放,意味着这些对象在堆内存层面彻底消失,其生命周期也就正式结束。

    • 对应的引用(如果还存在的话)只是失去指向的目标,不再代表任何有效的对象。


6. 总结

  • 类加载阶段:JVM 把类的字节码加载到方法区(或对应的元空间),并完成验证、准备、解析、初始化,使类处于可用状态。

  • 对象创建阶段:使用 new 等方式触发分配内存、初始化对象头和执行构造函数,并返回对象引用。

  • 对象使用阶段:对象在堆中存活,只要还有活动引用,JVM 就不会回收它;使用过程中可能触发 JIT 编译、逃逸分析等优化。

  • 可达性分析(垃圾回收判断):当 GC 触发时,使用可达性分析判断对象是否仍然与 GC Roots 可达。

  • 垃圾回收与销毁:被判断为不可达的对象会被垃圾收集器回收,其占用的堆内存被释放;如果类中实现了 finalize(),在老版本中会在回收前被调用,但现代 Java 中已不鼓励使用。

整体上,一个 Java 对象的生命周期基本就包含了**诞生(对象的分配、初始化)→ 存活(被引用、被使用)→ 回收(不可达、被 GC 回收)**这么一个过程。通过垃圾回收的机制,JVM 能够自动地管理对象内存,减少内存管理的复杂度,同时保证安全性和稳定性。

Last updated

Was this helpful?