java gc通过可达性分析(从gc roots出发)判断对象是否可回收,新生代用复制算法因存活率低,老年代用标记-整理因存活率高,system.gc()仅是建议且副作用大。

Java中的垃圾回收机制(GC)不是“自动清理内存”的模糊概念,而是一套基于可达性分析、分代管理、多算法协同的精密内存自治系统——它不依赖引用计数,不保证立即回收,也不承诺零停顿,但能大幅降低内存泄漏风险和开发负担。
GC怎么判断一个对象该被回收?不是看“有没有被new”,而是看“能不能从GC Roots触达”
Java不用引用计数法,因为循环引用(如objA.instance = objB且objB.instance = objA)会导致计数器永不归零,造成内存泄漏。JVM实际采用的是可达性分析算法:从一组固定的GC Roots出发,沿着引用链向下搜索。只要某对象无法被任何一条引用链抵达,它就被判定为“不可达”,即待回收垃圾。
常见的GC Roots包括:
-
虚拟机栈中正在执行的方法的局部变量所引用的对象 -
方法区中类的静态属性(static字段)引用的对象 -
方法区中常量池里引用的对象(如字符串常量"hello") -
本地方法栈中JNI调用所持有的对象句柄
注意:局部变量一旦超出作用域(比如方法return),即使没显式置null,只要栈帧弹出,其引用就自然消失——GC Roots随之丢失,对象立刻进入可回收候选。
立即学习“Java免费学习笔记(深入)”;
本文档主要讲述的是关于Objective-C手动内存管理的规则;在ios开发中Objective-C 增加了一些新的东西,包括属性和垃圾回收。那么,我们在学习Objective-C之前,最好应该先了解,从前是什么样的,为什么Objective-C 要增加这些支持。有需要的朋友可以下载看看
为什么新生代用复制算法,老年代却用标记-整理?这是由对象存活率决定的
分代设计不是拍脑袋定的,而是大量实证统计的结果:约98%的对象朝生暮死(只活1–2次Minor GC)。因此JVM把堆划为新生代(Eden + 2个Survivor)和老年代,并匹配不同算法:
-
新生代:存活对象少 → 用复制算法(如G1的Evacuation、ZGC的染色指针迁移)。好处是回收快、无碎片;代价是需预留一块空闲Survivor区做“搬运落脚点” -
老年代:对象存活率高、空间大 → 用标记-整理(如Serial Old)或标记-清除+压缩(如CMS已废弃,G1/ZGC靠并发移动)。若强行用复制,搬运成本太高,可能比停顿还伤性能
一个典型误操作:频繁创建长生命周期的大数组(如new byte[8 * 1024 * 1024]),它会直接绕过Eden,触发TLAB溢出或大对象直接分配到老年代,加剧Major GC压力——这不是GC机制失效,而是代码模式与分代假设冲突。
System.gc()真的能触发垃圾回收吗?能,但几乎不该用
System.gc()只是向JVM发出“建议”进行Full GC,是否执行、何时执行、用哪个收集器,完全由JVM自主决定(HotSpot默认开启-XX:+DisableExplicitGC时甚至直接忽略)。它带来的副作用远大于收益:
- 强制引发一次Stop-The-World暂停,可能卡住整个应用线程(尤其在生产环境高负载时)
- 干扰JVM自身的GC节奏,导致后续Minor GC更频繁(例如刚清完老年代,又因显式GC打乱了对象年龄晋升策略)
- 掩盖真实内存问题:比如本该优化缓存淘汰逻辑,却用
System.gc()临时“压低”内存曲线
真正需要干预GC的场景,应通过JVM参数而非代码调用:比如用-XX:+PrintGCDetails -Xlog:gc*定位对象泄漏,用-XX:MaxGCPauseMillis=200约束ZGC停顿目标,而不是在finally块里写System.gc()。
GC机制的复杂性不在“它做了什么”,而在“它为何这样权衡”——比如牺牲一半新生代空间换低延迟,容忍短暂碎片换吞吐,甚至允许部分对象“暂时漏收”来避免STW。理解这些取舍,比记住算法步骤更能帮你避开OOM和GC风暴。








