你住的小区里,总有一些邻居搬家后留下旧沙发、碎玻璃、空瓶子。物业保洁团队隔段时间就来清理一遍——能回收的放蓝桶,厨余扔绿桶,没人要的烂家具直接拉走。垃圾回收(GC)就是JVM里的保洁团队,帮你盯着堆内存里那些“没人住的房间”。
1. 先找到哪些房间没人住——可达性分析
你想想,物业怎么判断一个沙发是垃圾?不是看它旧不旧,而是看还有没有人用它。如果302房的租客早就搬走了,沙发扔在走廊落灰,那就是垃圾。
JVM里也一样,它从一群叫 GC Roots 的“根对象”出发(比如线程栈上的局部变量、静态变量、JNI引用),顺着引用链往下找。能被根对象顺着链摸到的对象,就是“还在住人的房间”;摸不到的,统统标记为垃圾。这个过程就叫可达性分析。
注意:这和Python等语言用的“引用计数”不同。引用计数就像给每个房间门口挂个牌子“当前住着X人”,但如果两个人互相指着对方说“他住我这儿”,牌子上的数字永远到不了0,就成死锁了。JVM的可达性分析直接从根出发,完美绕过这个坑。
2. 怎么打扫才高效?——分代收集理论
保洁阿姨不会全小区地毯式扫荡——太累。她发现一个规律:新搬进来的租客换得勤(今天住明天搬),老住户一住就是好几年。所以她把垃圾分成两类:
-
新生代:刚搬进来的年轻人。大部分住几天就走了(比如方法里的临时对象)。这里打扫频率高,用 复制算法——把还活着的少数人挪到另一块干净区域,原来那块直接清空。
比喻升级一下:小区准备了两间Survivor备用房(From和To),每次打扫时,把活人从一块挪到另一块,原地清空。两间房轮着用,效率极高。 -
老年代:住了好几年的钉子户。对象熬过多次GC(默认15岁,每扛过一次Minor GC长1岁)就晋升到这里。这里垃圾少,但打扫起来麻烦。
如果用“标记-清除”,就像只把垃圾扔出去却不整理房间——会留下大量内存碎片:总空间可能还够,但都是东一块西一块的小空隙,一个大冰箱(大对象)死活塞不进去。
所以老年代更常用标记-整理:清完垃圾后,把所有活着的对象往前挤紧,像整理书柜一样。代价是打扫期间所有人必须暂停活动——这就是著名的 STW(Stop The World)。
大白话:新来的用“挪窝清空法”,两间备用房轮着住;老住户用“标记再归置”,虽然要全场暂停,但能保证空间整齐。
3. 什么时候打扫?谁来打扫?
保洁团队不会24小时不停转。她要么等垃圾桶满了(内存分配失败,比如新生代放不下新对象),要么接到业主投诉(系统调用System.gc(),但JVM可以加参数-XX:+DisableExplicitGC直接无视它)。最常见的是:新生代满了先来一次 Minor GC(小扫除);老年代满了或系统觉得有必要时,来一次 Full GC(整栋楼大扫除,清理新生代+老年代+元空间)。
不同“保洁公司”干活方式不一样:
- Serial:单枪匹马,适合小内存,但打扫时全程停业。
- Parallel:多线程并行扫,吞吐量高,适合后台计算。
- CMS:边营业边打扫(大部分时间不暂停),但会产生“浮动垃圾”和碎片。
- G1:把整栋楼分成多个区域,优先清理垃圾最多的区,适合大内存堆。
你写代码时肯定遇到过 OutOfMemoryError。别以为真的是内存用光了——更常见的是内存碎片太多,或者达到了堆上限(比如-Xmx设了512M)。保洁团队摊手说:“我尽力了,但实在找不到一整块空地给你盖新房子。”
总结一下
| 概念 | 比喻 | 技术点 |
|---|---|---|
| 可达性分析 | 手电筒照活人,从根出发 | 避免循环引用,替代引用计数 |
| 新生代 | 新租客,走得快 | 复制算法(From/To),Minor GC |
| 老年代 | 老住户,稳定 | 标记-整理(解决碎片),Full GC |
| STW | 打扫时全场暂停 | 任何GC都有,但时长不同 |
| 内存碎片 | 房间空但支离破碎 | 标记-清除的致命缺陷 |
| 常见收集器 | 不同保洁公司 | Serial/Parallel/CMS/G1 |
面试官爱怎么问?
问:可达性分析和引用计数有什么区别?为什么JVM不用引用计数?
答:引用计数是每个对象记着自己被引用的次数,缺点是无法解决循环引用(A引B,B引A,但都没人用了)。JVM的可达性分析从GC Roots出发,顺着引用链往下扫,扫不到的对象就是垃圾,天然避免循环引用问题。
问:什么是STW?哪些GC阶段会发生?怎么减少?
答:STW就是Stop The World,垃圾回收时暂停所有用户线程。几乎任何GC的“标记”或“整理”阶段都会发生。减少STW的方法:选用并发收集器(如G1、ZGC),让大部分工作与用户线程同时进行;或者调小新生代大小,让Minor GC更快结束。
问:怎么定位内存泄漏?给个最简单的思路。
答:先用-XX:+PrintGCDetails看GC日志,如果老年代一直增长且Full GC频繁,大概率有泄漏。然后用MAT或Arthas dump出堆内存,对比GC Roots引用链,找到哪个对象不该活着但被某根引用一直拽着——比如静态集合、未注销的监听器。
人话总结
垃圾回收就是物业保洁团队——用手电筒照出没人住的房间,新房间用挪窝法(两间备用房轮换),老房间用归置法(但要全场暂停),满了就开扫。选对保洁公司(GC收集器),能让你家的卡顿少一大半。
汇总导航