# 8. 如何保证 JVM 里的对象不会被回收?
# 标准答案
在 Java 中,对象是否会被垃圾回收(GC)取决于 JVM 的 GC Root 可达性分析(Reachability Analysis)。如果对象仍然可以从 GC Root 访问,则不会被回收。为了保证对象不被回收,可以采取以下策略:
- 静态变量引用(static field):将对象存放在
static
变量中,如Singleton
。 - 强引用(Strong Reference):对象被强引用时,JVM 永远不会回收。
- 线程存活(ThreadLocal、线程对象):只要线程存活,线程持有的对象不会被回收。
- JNI 本地代码引用(Native Reference):使用
Unsafe
、JNI
直接分配的堆外内存不会被 GC。 - SoftReference / WeakReference:软引用在 OOM 前可能被回收,弱引用在 GC 时会被回收。
- GC Root 直接关联的对象:如
ClassLoader
持有的类对象不会被回收。 - 使用
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
2
3
- 只要
Singleton.class
没被卸载,instance
就不会被 GC。
# (3)线程存活(ThreadLocal / 线程变量)
线程对象存活时,它持有的变量不会被 GC。
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
threadLocal.set(new Object()); // 线程结束前不会被回收
1
2
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
2
# (6)同步锁定的对象
被 synchronized
锁定的对象,在释放锁之前不会被回收:
synchronized (obj) {
// obj 在锁内不会被 GC
}
1
2
3
2
3
# 3. 如何防止对象被错误回收?
(1)缓存对象:
使用 ConcurrentHashMap
进行对象缓存,确保对象一直可达。
Map<String, Object> cache = new ConcurrentHashMap<>();
cache.put("key", new Object()); // 保证对象可达
1
2
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
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
# 深入追问
SoftReference
在 GC 时一定会被回收吗?ClassLoader
什么时候会触发对象回收?ThreadLocal
造成内存泄漏的原理是什么?如何避免?WeakHashMap
如何保证对象被 GC 回收?
# 相关面试题
System.gc()
可以防止对象回收吗?- 强引用、软引用、弱引用、虚引用的区别?
SoftReference
和WeakReference
在 GC 时的回收策略?- 为什么
WeakHashMap
适合作为缓存?