JVM-垃圾回收器与内存分配策略详解
1.如何判断对象已死
1.1 对象引用的4种类型(强软弱虚)
1.1.1 强引用
特点:最常见的引用类型,只要强引用存在,对象绝不会被回收
Object strongObj = new Object(); // 强引用
注意:集合类型,如果对象不再使用,应当及时清除,避免内存泄漏
1.1.2 软引用
特点:内存不足时才会被回收,适合实现缓存
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024*1024*10]); // 10MB
适用场景:
- 缓存(EhCache中使用软引用,一旦触发内存不足时,就会由JVM自动清理数据,释放内存)
缓存本身的含义:可删除
1.1.3 弱引用
特点:下次GC时必定被回收,适合维护非必要数据
// 弱引用基本示例
WeakReference<String> weakRef = new WeakReference<>(new String("Weak Object"));
适用场景:
临时的数据
1.1.4 虚引用
1.1.5 其他
1.1.5.1 ReferenceQueue
referenceQueue(引用队列)是Java引用类型体系中的关键组件,与SoftReference、WeakReference和PhantomReference配合使用,提供了对象生命周期监控的机制。
- 通知机制:当被引用的对象达到相应回收状态时,引用对象本身会进入队列
- 非阻塞设计:提供poll()和remove()两种获取方式
- GC协作:由垃圾收集器在适当时候自动入队
工作流程
创建引用对象 → 关联ReferenceQueue → 对象被回收 → 引用对象入队 → 客户端处理
引用类型 | 入队时机 | 典型用途 |
---|---|---|
虚引用 | 对象被回收且finalize完成后 | 精准资源清理 |
弱引用 | 对象被GC回收时 | 缓存/临时映射 |
软引用 | 内存不足被回收时 | 内存敏感缓存 |
强引用 | 从不入队 | 常规对象引用 |
1.1.6 使用案例
软引用缓存,清除无效的key
/*** 弱引用缓存*/public class WeakCache<K, V> {private final ConcurrentHashMap<K, WeakReference<V>> cache = new ConcurrentHashMap<>();private final ReferenceQueue<V> queue = new ReferenceQueue<>();public void put(K key, V value) {cleanUp();cache.put(key, new WeakReference<>(value, queue));}// 清除缓存中已释放的引用(原因:value是弱引用、但是key是强引用,value被释放了,key没有作用,应当也被释放)private void cleanUp() {Reference<? extends V> ref;while ((ref = queue.poll()) != null) {Reference<? extends V> finalRef = ref;cache.values().removeIf(weakRef -> weakRef == finalRef);}}
}
1.2 判断对象已死
1.2.1 方案1:引用计数算法
核心思想
- 每个对象维护一个引用计数器,记录有多少引用指向它
计数规则
- 新引用指向对象时,计数器+1
- 引用失效时,计数器-1
- 计数器=0时立即回收对象
1.2.2 方案2:可达性分析算法
核心思想
- 通过GC Roots作为起点,遍历对象引用链
判定规则
- 从GC Roots不可达的对象判定为垃圾
- 通常在特定时间点(如堆空间不足时)执行
GC Roots有哪些
GC Roots是垃圾回收的起点,所有从这些根对象直接或间接可达的对象都被视为存活对象。以下是Java中常见的GC Roots类型:
- 虚拟机栈(栈帧中的本地变量表)中的引用对象
public void method() {Object localObj = new Object(); // localObj是GC Root// 方法执行期间,localObj引用的对象不会被回收
}当前所有正在执行的方法中的局部变量和参数包括Java方法、native方法的栈帧中的引用
- 方法区中类静态属性引用的对象
class MyClass {static Object staticObj = new Object(); // staticObj是GC Root
}特点:类的静态变量引用的对象直到ClassLoader卸载前都会保持强引用
- 方法区中常量引用的对象
class MyClass {static final String CONSTANT = "常量"; // CONSTANT是GC Root
}特点:包括字符串常量池中的引用编译期常量(constantValue属性)不算真正的GC Root
- 本地方法栈中JNI(Java Native Interface)引用的对象
public native void nativeMethod(Object obj); // native代码中的obj引用Java调用native方法时传入的参数对象
全局JNI引用(NewGlobalRef创建)也是GC Root
- 被同步锁(synchronized)持有的对象
- Java虚拟机内部的引用
包括:
基本类型对应的Class对象
常驻异常对象(NullPointerException等)
系统类加载器
类加载器管理的Class对象
1.2.3 工作原理对比
特征 | 引用计数 | 可达性分析 |
---|---|---|
触发时机 | 实时(引用变更时) | 周期性/按需 |
执行速度 | 分散的微小停顿 | 集中式停顿(STW问题) |
内存回收 | 立即回收 | 延迟回收 |
实现复杂度 | 简单 | 复杂 |
对象判定 | 计数器=0 | GC Roots不可达 |
1.2.4 优缺点分析
1.2.4.1 引用计数的优缺点
优点:
- 立即回收内存,减少内存占用
- 垃圾回收开销平均分配到程序运行中
- 不需要STW(Stop-The-World)暂停
缺点:
- 计数器维护带来额外内存开销
- 频繁更新计数器影响性能
- 循环引用问题(主要缺陷)
// 引用计数无法处理的场景
class A { B b; }
class B { A a; }void createCycle() {A a = new A(); // A计数=1B b = new B(); // B计数=1a.b = b; // B计数=2b.a = a; // A计数=2// 即使外部引用消失...a = null; // A计数=1b = null; // B计数=1// 对象仍无法回收!
}// 可达性分析可以正确处理
1.2.4.2 可达性分析的优缺点
优点:
- 能正确处理循环引用
- 回收效率高(集中处理)
- 现代JVM采用的成熟方案
缺点:
- 需要STW暂停应用线程
- 实现复杂度高(需要准确识别GC Roots)
- 内存回收不及时
java采用可达性分析算法
2.垃圾收集算法
分代收集理论
标记-清除算法
标记-复制算法
标记-整理算法
算法细节(根节点枚举、安全点、安全区域、记忆集与卡表、写屏障、并发的可达性分析)
3.常见的垃圾收集器
3.1 serial
3.2 parNew
3.3 Parallel Scavenge
3.4 serial old
3.5 Parallel Old
3.6 CMS
3.7 G1
3.8 ZGC
4. 内存分配策略
对象优先分配到Eden区
大对象直接进入老年代
长期存活的对象将进入老年代
动态对象年龄判断
空间分配担保
垃圾回收的优化案例
fullGc问题
对于绝对不可接受Full GC的系统:
- 商用解决方案:Azul Zing(C4 GC)、IBM Balanced GC
- 内存架构改造:
- 堆外内存存储(HBase/OFF-Heap)
- 分布式缓存集群
- 服务拆分:将内存敏感组件独立部署
最佳实践:某证券交易所系统通过G1+堆外缓存方案,实现连续18个月零Full GC,老年代内存通过并发标记和定期混合回收保持85%以上可用率。