# 8. 如何保证 JVM 里的对象不会被回收?

# 标准答案

在 Java 中,对象是否会被垃圾回收(GC)取决于 JVM 的 GC Root 可达性分析(Reachability Analysis)。如果对象仍然可以从 GC Root 访问,则不会被回收。为了保证对象不被回收,可以采取以下策略:

  1. 静态变量引用(static field):将对象存放在 static 变量中,如 Singleton
  2. 强引用(Strong Reference):对象被强引用时,JVM 永远不会回收。
  3. 线程存活(ThreadLocal、线程对象):只要线程存活,线程持有的对象不会被回收。
  4. JNI 本地代码引用(Native Reference):使用 UnsafeJNI 直接分配的堆外内存不会被 GC。
  5. SoftReference / WeakReference:软引用在 OOM 前可能被回收,弱引用在 GC 时会被回收。
  6. GC Root 直接关联的对象:如 ClassLoader 持有的类对象不会被回收。
  7. 使用 synchronized 锁住对象:被 synchronized 关键字锁定的对象不会立即被回收。

# 答案解析

# 1. GC Root 可达性分析

JVM 采用 GC Root 可达性分析算法 来决定对象是否存活。GC Root 主要包括:

  • JVM 栈(虚拟机栈):栈帧中引用的局部变量不会被回收。
  • 方法区的静态变量static 变量持有对象时,对象不会被回收。
  • 方法区的常量引用:字符串常量池中的字符串对象(intern)。
  • 本地方法栈(Native 方法):JNI 引用的对象不会被 GC。
  • 活动线程:线程对象存活,其持有的对象不会被回收。

# 2. 常见的对象持久化方式

# (1)强引用(Strong Reference)

强引用对象永远不会被 GC,除非手动置 null

Object obj = new Object(); // 强引用
1

如果对象 obj 还可以被访问,JVM 绝不会回收它。

# (2)静态变量(Static Field)

static 变量属于 类元数据,只要 ClassLoader 没有卸载,static 持有的对象不会被回收。

class Singleton {
    private static final Singleton instance = new Singleton();
}
1
2
3
  • 只要 Singleton.class 没被卸载,instance 就不会被 GC。
# (3)线程存活(ThreadLocal / 线程变量)

线程对象存活时,它持有的变量不会被 GC。

ThreadLocal<Object> threadLocal = new ThreadLocal<>();
threadLocal.set(new Object()); // 线程结束前不会被回收
1
2

如果 ThreadLocal 没有 remove(),它的引用对象可能会导致内存泄漏

# (4)JNI 本地引用

JNI 代码可以持有 Java 对象的引用,使其不被 GC 回收。

System.loadLibrary("nativeLib"); // 调用 C++ 代码持有对象
1
  • Unsafe.allocateMemory() 分配的堆外内存不会被 GC。
# (5)弱引用(WeakReference)

WeakReference 引用的对象,在 GC 时会立即被清理。

WeakReference<Object> weakRef = new WeakReference<>(new Object());
System.gc(); // 立即被回收
1
2
# (6)同步锁定的对象

synchronized 锁定的对象,在释放锁之前不会被回收:

synchronized (obj) {
    // obj 在锁内不会被 GC
}
1
2
3

# 3. 如何防止对象被错误回收?

(1)缓存对象
使用 ConcurrentHashMap 进行对象缓存,确保对象一直可达。

Map<String, Object> cache = new ConcurrentHashMap<>();
cache.put("key", new Object()); // 保证对象可达
1
2

(2)使用 SoftReference 实现缓存

SoftReference<byte[]> cache = new SoftReference<>(new byte[10 * 1024 * 1024]); 
1
  • 在内存充足时SoftReference 不会被回收。
  • 当 OOM 发生时,软引用会被回收,避免内存泄漏。

(3)避免 ThreadLocal 内存泄漏
手动 remove() 避免线程对象泄漏:

threadLocal.remove();
1

(4)慎用 finalize() 方法

  • finalize() 会延迟对象回收,可能导致对象复活
protected void finalize() throws Throwable {
    obj = this; // 复活对象
}
1
2
3

finalize() 不可靠,JDK 9+ 已废弃。

# 4. 如何避免对象不被回收导致 OOM?

  • 避免 static 变量持有大对象(如 List)。
  • 使用 WeakReference 代替 StrongReference
  • 避免 ThreadLocal 不手动清理
  • 监控 Metaspace,避免 ClassLoader 泄漏
    jstat -gcmetacapacity <pid>
    
    1
  • 定期分析 Heap Dump,查找不可回收对象
    jmap -dump:format=b,file=heap.bin <pid>
    
    1

# 深入追问

  1. SoftReference 在 GC 时一定会被回收吗?
  2. ClassLoader 什么时候会触发对象回收?
  3. ThreadLocal 造成内存泄漏的原理是什么?如何避免?
  4. WeakHashMap 如何保证对象被 GC 回收?

# 相关面试题

  • System.gc() 可以防止对象回收吗?
  • 强引用、软引用、弱引用、虚引用的区别?
  • SoftReferenceWeakReference 在 GC 时的回收策略?
  • 为什么 WeakHashMap 适合作为缓存?