本文将从对象完整生命周期的角度,全面解析JVM的内存管理与垃圾回收机制。你将深入了解一个对象从创建、存活、晋升到最终被回收的全过程。
整个过程由JVM内存模型提供底层支持,确保多线程环境下的安全性与一致性。
在探讨对象诞生之前,需先了解JVM为其准备的运行环境——即运行时数据区的整体结构。
执行如下代码:
Object obj = new Object();
JVM内部会完成一系列复杂操作:
<init>方法,按照程序逻辑对对象进行初始化<init>
其中,Mark Word 在不同位宽虚拟机中的结构如下:
引用计数法(如Python使用)
可达性分析算法(Java采用的核心机制)
常见的GC Roots来源包括:
定义方式:最常见的赋值形式。
Object obj = new Object(); // obj 是强引用
String str = "Hello"; // str 也是强引用
关键特性:
回收时机:
典型用途:日常编码中绝大多数对象引用均为强引用,是程序运行的基础支撑。
创建方式:借助SoftReference类封装对象。
// 创建原始对象
Object strongRef = new Object();
// 构建软引用
SoftReference<Object> softRef = new SoftReference<>(strongRef);
// 可选:结合引用队列监控回收事件
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> softRefWithQueue = new SoftReference<>(strongRef, queue);
// 移除强引用,仅保留软引用
strongRef = null;
// 使用时尝试获取目标对象
Object target = softRef.get();
if (target != null) {
// 对象尚未被回收,可以正常使用
} else {
// 已被回收,需要重新创建
}
核心行为:
适用场景:适合实现内存敏感的缓存机制,例如图片缓存、临时数据存储等。
Object obj = new Object()
OutOfMemoryError
obj
null
java.lang.ref.SoftReference
当系统内存紧张时,垃圾收集器会优先回收仅被软引用所指向的对象。这种回收操作会在 OutOfMemoryError 被触发之前进行,从而尽可能避免程序崩溃。
回收时机:在内存不足的情况下触发。
典型应用场景:适用于实现对内存使用敏感的缓存机制。
通过 WeakReference<T> 类创建弱引用:
Object strongRef = new Object();
WeakReference<Object> weakRef = new WeakReference<>(strongRef);
// 解除强引用
strongRef = null;
// 显式触发GC(仅用于演示,生产环境不推荐)
System.gc();
// GC执行后,weakRef.get() 很可能返回 null
if (weakRef.get() == null) {
System.out.println("对象已被GC回收");
}
核心特性:无论当前内存是否充足,只要发生垃圾收集,且目标对象仅被弱引用指向,则该对象就会被回收。其生命周期比软引用更短。
回收时机:下一次垃圾收集运行时即可能被清除。
常见使用场景包括:
WeakHashMap 中的键采用弱引用方式。若某个键对象除了在 WeakHashMap 中存在弱引用外,无任何强引用关联,则在下次GC时,对应的键值对将被自动移除。这一特性常用于缓存对象元数据,当原对象不可达时,相关辅助信息也随之清理。ThreadLocalMap 键也采用了弱引用设计,以减少内存泄漏风险。不过其值仍为强引用,因此在使用完毕后必须手动调用 remove() 方法清除,否则仍可能导致内存累积。java.lang.ref.WeakReference
虚引用需通过 PhantomReference<T> 类配合引用队列(ReferenceQueue)共同使用:
Object strongRef = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(strongRef, queue);
// 移除强引用
strongRef = null;
// 注意:phantomRef.get() 始终返回 null,无法获取原对象
System.gc(); // 触发垃圾收集
// 检查引用队列是否有元素出队
Reference<?> ref = queue.poll();
if (ref != null) {
System.out.println("检测到对象被回收,可以执行后续清理工作");
// 通常在此处释放堆外内存或其他资源
}
核心特性:
get() 方法始终返回 null get()null。回收时机:处于对象被回收的最终阶段。设置虚引用相当于标记对象“已判死刑”,而虚引用就像外部观察者,能准确得知“行刑”事件的发生时间。
典型用途:
DirectByteBuffer)不受 GC 控制。此时可在 Java 堆中创建一个轻量级对象代表大块堆外内存,并为其设置虚引用。当该对象被 GC 回收时,JVM 会将其对应的虚引用加入队列,程序便可据此信号主动释放对应的堆外内存,防止内存泄漏 Unsafe。java.lang.ref.PhantomReference
ReferenceQueue
| 引用类型 | 创建方式 | 垃圾回收时机 | 生存时间(强度) | 用途 |
|---|---|---|---|---|
| 强引用 | |
永远不会被回收 | 最强 | 程序默认的对象引用方式,所有常规对象均为此类 |
| 软引用 | |
内存不足时 | 较强 | 用于实现内存敏感型缓存,如图片、计算结果缓存 |
| 弱引用 | |
下一次GC时 | 较弱 | 应用于 WeakHashMap、防止内存泄漏的临时结构等 |
| 虚引用 | |
对象被回收的最终时刻 | 最弱(无法获取对象) | 跟踪对象回收事件,常用于堆外内存资源的释放 |
get()
null
DirectByteBuffer
Unsafe标记-清除算法:
复制算法:
标记-整理算法:
基于“弱分代假说”与“强分代假说”,Java堆通常被划分为两个逻辑区域:
新生代(Young Generation):
老年代(Old Generation):
跨代引用处理机制:
| 类型 | 新生代回收器 | 老年代回收器 | 核心特性 |
|---|---|---|---|
| 串行回收器 | Serial | Serial Old | 单线程运行,STW时间较长,适用于客户端场景 |
| 并行回收器 | ParNew, Parallel Scavenge | Parallel Old | 多线程并行处理,侧重提高吞吐量 |
| 并发回收器 | - | CMS, G1, ZGC | 尽可能减少停顿时间,强调低延迟 |
CMS(Concurrent Mark-Sweep)回收器:
G1(Garbage-First)回收器:
ZGC 与 Shenandoah 回收器:
JVM内存模型(JMM)为GC提供了关键支持:
// 在GC过程中,如果没有STW,可能发生:
// 线程A:读取对象O的字段f
// GC线程:移动对象O到新位置
// 线程A:使用字段f(此时对象已移动,可能访问到错误内存)
// JMM通过STW保证在GC过程中对象引用关系不会变化
以下是一个典型的Spring MVC控制器方法,展示对象从创建到回收的全过程:
@RestController
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
// 1. 参数id作为局部变量存储在栈帧中(可能经逃逸分析优化为栈上分配)
// 2. 查询返回的User实例在Eden区完成内存分配
User user = userService.findById(id);
// 3. 方法调用结束时,user引用从栈中弹出,但堆中对象仍存在
// 4. 若请求频繁,Eden区迅速填满,触发一次Minor GC
// 5. 若此User对象被缓存等外部结构引用,则在可达性分析中判定为存活
// 6. 经历多次Young GC仍未被释放,最终晋升至老年代
// 7. 缓存过期后,对象彻底不可达,等待下一次Full GC将其回收
return user;
}
}

扫码加好友,拉您进群



收藏
