垃圾回收原理(通俗版)
垃圾回收原理
一、GC是什么
场景类比:
你的家 = JVM内存
家具物品 = Java对象
保洁阿姨 = 垃圾收集器
打扫过程:
1. 识别垃圾:阿姨检查哪些是真垃圾(没人用的对象),哪些是还要用的(被引用的对象)
2. 分区打扫:
1. 客厅(新生代):每天打扫,速度快,但只收拾表面垃圾
2. 储藏室(老年代):每周大扫除,时间长,但收拾得彻底
3. 书房(元空间):存放重要文件,很少清理
二、GC核心原理:三部曲
第一步:找垃圾--谁没人要了
关键算法:
- 引用计数:数一数多少个人指着这个对象说 我要
- 可达性分析:从祖宗对象出发,能找到的就是有用的
第二步:收垃圾--怎么收拾
- 新生代打扫法
- 把还要的家具搬到隔壁房间(Survivor区)
- 把不要的家具扔掉(清理Eden区)
- 来回版几次还用的家具,搬到储藏间(晋升到老年代)
- 老年代打扫法
- 标记-整理:把所有要的家具靠墙放好,中间空出来
- 标记-清除:直接把不要的家具扔掉(有碎片)
第三步:倒垃圾--空间再利用
清理出来的空间,就像腾出的空地,可以放新家具(新对象)
三、GC性能瓶颈(哪里会卡住)
瓶颈1:STW(stop the world)- 全员暂停
保洁阿姨打扫时喊:"所有人停下!不许动!"
程序暂停 → 用户觉得卡顿
表现:应用响应变慢,请求超时
瓶颈2:频繁Minor GC - "老喊暂停"
客厅太小,刚打扫完又乱了
导致:阿姨每分钟都要喊一次暂停
原因:新生代空间太小或对象创建太快
瓶颈3:Full GC太久 - "大扫除时间太长"
储藏室东西太多,阿姨打扫要1小时
这期间全家人都得等着
常见原因:
- 老年代空间不足
- 内存泄漏(东西只进不出)
- 大对象直接进老年代
瓶颈4:内存碎片 - "空间浪费"
储藏室:有空位但放不下新家具
因为位置太分散,每个空位都很小
影响:明明有空闲内存,却说内存不足
瓶颈5:错误的GC策略--用错保洁阿姨
需要快速响应时请了慢工出细活的阿姨
需要省电时请了动作快但费力的阿姨
匹配问题:
- Web应用 → 需要低延迟(CMS、G1、ZGC)
- 批处理 → 需要高吞吐量(Parallel GC)
四、场景问题场景
案例1:电商秒杀
问题:瞬间创建大量订单对象
表现:频繁Young GC,CPU飙升
解决:增大年轻代,使用G1收集器
案例2:后台报表系统
问题:生成大报表对象
表现:老年代快速填满,频繁Full GC
解决:增加老年代大小,调整晋升阈值
案例3:缓存服务
问题:缓存对象长时间存活
表现:老年代占用高,Minor GC效率低
解决:使用CMS或G1,减少STW时间
五、优化思路
原则1:让对象尽快死掉
// 不好:对象活太久
List<String> cache = new ArrayList<>(); // 一直引用
// 好:及时释放
{
List<String> temp = new ArrayList<>();
// 用完后自动回收
}
原则2:选择合适的“保洁阿姨”
- 场景:小应用 推荐GC:Serial GC 特点:单线程,简单
- 场景:后台计算 推荐GC:Parallel GC 特点:吞吐量优先
- 场景:web服务 推荐GC:G1 GC 特点:平衡吞吐和延迟
- 场景:大内存 推荐GC:ZGC/Shenandoah 特点:超低延迟
原则3:合理分配“房间大小”
# 常用参数
-Xms4g -Xmx4g # 总堆大小
-XX:NewRatio=2 # 老年代:新生代=2:1
-XX:SurvivorRatio=8 # Eden:Survivor=8:1:1
六、监控和诊断(怎么发现问题)
1、看症状:
应用卡顿----》可能full GC
CPU一直高 ---》可能频繁GC
内存增长不停 ---》可能内存泄漏
2、用工具
# 查看GC情况
jstat -gcutil <pid> 1000
# 生成堆快照
jmap -dump:format=b,file=heap.hprof <pid>
# 实时监控
jvisualvm # 图形化工具
3、关键指标
- GC频率:多久一次?
- GC时间:每次停多久
- 吞吐量:GC时间/总时间 < 5%
- 停顿时间:单次停顿< 1000ms
总结:
GC就像打扫卫生,太频繁打扫影响生活(性能差),太久不打扫房子乱了(内存溢出),大扫除太久全家等待(卡顿)。关键是要让垃圾早点产生(对象及时释放),分区打扫(年轻/年老代分开),选对保洁员(合适的GC算法),监控清洁效果(看GC日志)。没有最好的GC,只有合适的GC。
浙公网安备 33010602011771号