关键在于避免包装类被长期强引用,因其作为对象会堆积在老年代;应使用带TTL的缓存、基本类型原子类、局部变量等替代静态包装类集合,并通过jstat、MAT等工具监控和定位泄漏。

关键不在“怎么用”,而在“别让包装类被长期强引用”。基本类型本身不占堆、不参与 GC,天然安全;问题出在包装类——它们是真实对象,一旦被静态集合、缓存、监听器等长生命周期组件持有着,就会堆积不释放。
避免静态集合无限制累积包装类
日志计数器、灰度开关、指标埋点等场景,常写成:
static List
这看似 harmless,但每次 add 都可能新增一个 Integer 对象(超出 -128~127 缓存范围时一定新建),而 static 引用链让它永远活在老年代。
- 改用带自动过期的结构:Caffeine 或 ExpiringMap,明确设定 TTL,例如
Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES) - 若必须用静态容器,确保有清理机制:不是只调
list.clear(),还要确认没有其他 static 变量或闭包仍持有该 list 引用 - 优先用基本类型数组或 LongAdder 等原子类替代包装类集合,比如计数直接用
static final LongAdder counter = new LongAdder()
警惕自动装箱在循环和高频路径中的滥用
这段代码很常见,但风险隐蔽:
for (int i = 0; i
如果 map 是静态的、或生命周期远超单次请求,这些 Integer 就会滞留。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 高频写入场景,禁用自动装箱:显式传基本类型参数(如方法签名用
int而非Integer),或用原始类型专用集合库(如 Eclipse Collections 的IntArrayList) - Map 的 value 若只是标志位(true/false),别用
Boolean.TRUE,改用boolean字段或位运算标记 - 临时计算中避免不必要的装箱:如
if (obj.getValue() != null && obj.getValue() > 0)比if (Objects.nonNull(obj.getValue()) && obj.getValue() > 0)更少触发拆箱
慎用 WeakHashMap 代替“弱引用”幻想
有人以为 WeakHashMap<string integer></string> 能防泄漏——其实只对 key 弱引用,value(Integer)仍是强引用,照样钉住内存。
- WeakHashMap 适合 key 是外部对象、需随 key 消亡而自动清理的场景(如缓存与 UI 组件绑定),不适用于 value 是包装类的计数/状态存储
- 真要弱化 value,得自己封装:例如用
WeakReference<integer></integer>作 value,但要注意 null 检查和并发安全 - 更稳妥的做法是根本不用静态 map 存包装类,改用请求级局部变量或 ThreadLocal(注意 remove)
用工具早发现,别等 OOM
这类泄漏不会立刻爆 OOM,而是缓慢推高老年代,常规监控难捕获。
- 定期跑
jstat -gc <pid></pid>,盯住 OU(Old Used)是否持续缓慢上涨,且多次 Full GC 后不回落 - 导出堆快照后,在 MAT 中按
java.lang.Integer排序,重点看 “Number of Objects” 是否达万级、Retained Heap 总和是否异常大,并用 Path to GC Roots 确认终点是某个 static field - 压测时加入内存增长趋势观察项,尤其关注长时间运行后老年代占用斜率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










