Java垃圾回收需动手实践:通过GC Roots判定对象存亡(如栈变量、静态字段等),结合日志参数(-XX:+PrintGCDetails)观察Minor/Full GC行为,按业务选引用类型(强/软/弱/虚),并避开参数误配(如PretenureSizeThreshold对G1无效)等典型陷阱。

Java 垃圾回收不是背概念,是练判断、练配置、练日志、练场景。真正掌握,得动手拆解每一步逻辑,而不是默写算法名字。
怎么判定一个对象是不是垃圾?先盯住 GC Roots
GC Roots 不是抽象概念,是 JVM 明确列出的几类“活源头”:
- 方法栈里正在用的局部变量(比如
User u = new User(),方法没结束,u 就是根) - 类的静态字段指向的对象(如
public static List<String> cache = new ArrayList<>()) - 字符串常量池里的字面量(如
String s = "hello",“hello”在常量池里,就是根) - 被 synchronized 锁住的对象(说明正被线程使用,不能动)
- JNI 调用中 C 代码持有的 Java 对象句柄
练习方法:随手写一段代码,画出所有引用链,标出哪些对象能从上述任意一个根出发到达;断开某条引用后,再重画——看谁掉链子、谁变孤岛。循环引用(A→B,B→A)只要脱离 GC Roots,立刻被回收,这就是可达性分析的威力。
怎么看出 GC 正在做什么?靠参数 + 日志 + 观察行为
光看理论没用,必须让 JVM 把过程“说出来”:
- 加
-XX:+PrintGCDetails -XX:+PrintGCDateStamps启动程序,每次 GC 都会输出时间、各代内存变化、耗时 - 关注日志关键词:
PSYoungGen表示新生代回收(Minor GC),ParOldGen或CMS表示老年代动作,Full GC是全堆扫描 - 想验证大对象是否直入老年代?设
-XX:PretenureSizeThreshold=4194304(4MB),然后new byte[4_200_000],再看日志里它有没有出现在 PSYoungGen 分配记录中——没出现,只在 ParOldGen 里涨了,就成功了
注意:-XX:PretenureSizeThreshold 只对 Serial/ParNew 收集器有效,用 G1 就完全忽略,别白配。
怎么选引用类型?看它该活多久、要不要随 GC 自动消失
不是“越弱越好”,而是按业务契约选:
- 强引用:默认行为。只要变量还在作用域、静态集合还 hold 着它,就绝不回收。适合核心业务对象
- 软引用:缓存场景。内存快撑不住时才清,比如图片加载库用
SoftReference<Bitmap>,OOM 前自动释放 - 弱引用:附属关系。GC 一来就走,比如
WeakHashMap的 key,避免 map 长期持有 controller 导致内存泄漏 - 虚引用:纯通知用途。
get()永远返回 null,唯一价值是配合ReferenceQueue感知对象被回收的瞬间,用于清理 native 资源或关闭文件句柄
写个测试:往 WeakHashMap 里 put 一个 key,然后 System.gc(),紧接着 map.size() 很可能变成 0——这就是弱引用的真实手感。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
怎么避开典型坑?从参数、收集器、堆布局三处下手
很多 OOM 不是代码写错,是配置和认知偏差:
-
-XX:PretenureSizeThreshold设太大(比如 16MB),但业务最大数组才 5MB,白白增加判断开销,还可能因老年代碎片无法分配而触发 Full GC - 切换到 G1 后还留着
-XX:PretenureSizeThreshold,它已失效;G1 处理大对象用的是 Humongous Region,要看-XX:G1HeapRegionSize和实际对象大小是否 ≥ ½ Region - 老年代空间足够但碎片化严重(尤其 CMS 未开启
+UseCMSCompactAtFullCollection),即使对象满足阈值也进不去,直接 Full GC
调优不是调单个参数,而是看整体:用 jstat -gc <pid> 查实时各代使用率与 GC 次数,用 jmap -histo <pid> 看哪类对象占堆最多,再结合代码定位根源。
不复杂但容易忽略

















