面试的时候,Java GC 是个绕不开的话题。Minor GC、Full GC、Stop-The-World——这些词都听过。但真让你看一段代码,说出"GC 在哪一刻发生,回收了哪些对象",不少人就开始含糊了。
这一篇,我用一个完整的例子,把 GC 的来龙去脉走一遍。不背八股文,只看代码和现象。
一、先有个心理模型:JVM 堆长什么样
在 HotSpot JVM 里,堆(Heap)大致是这样划分的:
HotSpot 堆内存结构
新对象绝大多数分配在 Eden 区。Eden 满了,就触发 Minor GC,把活下来的对象挪到 Survivor(S0/S1),熬过 15 次(默认)的对象晋升到老年代。老年代也满了,就触发 Full GC——这是大家最不想看到的。
二、写一段会"动"的代码
下面这段代码,会制造出"新对象不断产生"和"老对象常驻"两种情况:
public class GC { // 老对象:被静态变量引用,不会被回收 private static List<Object> longLived = new ArrayList<>(); public static void main(String[] args) { // 每轮往老对象集合里塞一个 10MB 的大数组 while (true) { byte[] buf = new byte[10 * 1024 * 1024]; // 10MB longLived.add(buf); // 临时对象:循环结束就没人引用,等下一次 Minor GC String msg = "hi-" + System.currentTimeMillis(); System.out.println("已分配 " + longLived.size() + " 个 10MB 数组"); } } }
运行的时候,我们可以加几个 JVM 参数,让 GC 行为"显形":
java -Xms200m -Xmx200m \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
GC.java
-Xms 和 -Xmx 都设 200MB,堆固定大小,好观察。打印 GC 日志让我们看清每一次 GC 触发时机。
三、观察日志:GC 何时发生
跑起来,日志大概长这样(简化过):
已分配 1 个 10MB 数组 已分配 2 个 10MB 数组 ... [GC (Allocation Failure) 78M -> 24M(200M), 0.012 secs] Eden: 156M -> 0M Survivor: 0M -> 8M Old: 16M -> 24M [Full GC (Ergonomics) 198M -> 188M(200M), 0.085 secs] Eden: 0M -> 0M Survivor: 0M -> 0M Old: 188M -> 188M Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
解读一下:
第一次 Minor GC
Eden 区被填满(我们持续分配新对象),触发 Allocation Failure,JVM 启动 Minor GC。Survivor 装不下的就晋升到老年代,这就是为什么 Old 也在涨。
后来的 Full GC
老年代快满了,JVM 决定来一次"大扫除"。Full GC 会暂停所有应用线程(Stop-The-World),把整个堆都扫一遍,试图腾出空间。
最终的 OOM
但我们的 longLived 一直持有所有 byte[],Full GC 也回收不掉任何东西,最后 OutOfMemoryError。
关键观察: 不是所有 GC 都能"解决问题"。如果存在内存泄漏(对象一直被强引用),GC 再勤快也救不了你。这就是为什么光会调 GC 参数不够,得先排查泄漏。
四、GC 何时触发?一张表说清楚
GC 触发条件速查
五、怎么调优?三条朴素的建议
1. 让对象"早死"
能用局部变量,就别用静态集合。能早 null 的大对象,就别留到方法结束。Minor GC 越频繁,但每次越快,远比一次 Full GC 友好。
2. 合理设置堆大小
-Xms 和 -Xmx 建议设成一样,避免堆动态扩容带来的性能波动。新生代:老年代 经验值 1:2。
3. 选对收集器
JDK 8 默认 Parallel GC(吞吐优先);JDK 9+ 默认 G1(延迟优先);ZGC 适合超大堆、低延迟场景。没有银弹,看业务挑。
六、写在最后
GC 是 Java 程序员绕不开的"老朋友"。它的设计很优雅——把内存管理的脏活累活交给运行时,让我们专心写业务。
但它也不是银弹。写出能被回收的对象,比调 GC 参数重要一百倍。愿你下一次看到 GC 日志时,不再是"它在干嘛?",而是"哦,我知道这一刻发生了什么"。
One-liner takeaway
新对象进 Eden,Eden 满就 Minor GC;活的进 Survivor,熬过 15 次进老年代;老年代满就 Full GC,Stop-The-World。让对象"早死",比调参更重要。