Java对象分配始于Eden区,新对象优先在Eden或TLAB中分配;Eden满触发Minor GC,存活对象移入Survivor并年龄+1;年龄达阈值(默认15)或Survivor空间不足时晋升老年代;大对象超-XX:PretenureSizeThreshold则直接入老年代。

分析 Java 对象在堆内存中的分配行为,核心是理解对象如何被创建、分配到哪块内存区域(如 Eden、Survivor、Old)、何时晋升、以及是否触发 GC。这需要结合 JVM 参数配置、GC 日志、工具采样和代码特征综合判断。
观察 GC 日志定位分配与晋升行为
启用详细 GC 日志(如 -Xlog:gc*,gc+age=trace 或旧版 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps),重点关注:
- 每次 Young GC 后 Eden 区使用量清零,Survivor 区对象复制及年龄增长(Age 字段)
- 对象从 Survivor 晋升到 Old 的阈值(-XX:MaxTenuringThreshold)与实际晋升年龄是否匹配
- Old 区增长速率快,可能说明大对象直接分配(-XX:PretenureSizeThreshold 生效)或 Survivor 空间不足导致提前晋升
识别大对象与直接分配到老年代
超过 -XX:PretenureSizeThreshold(默认 0,即禁用)的数组或对象,JVM 会跳过年轻代,直接在 Old 区分配。常见于:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 大 byte[]、char[](如读取文件、序列化缓存)
- 显式设置该参数后,可通过 GC 日志中 “Promotion failed” 或 “Direct allocation in old” 类提示确认
- 注意:CMS 不支持该参数;G1 中由 -XX:G1HeapRegionSize 和对象大小决定是否直接进入 Humongous 区
借助 JFR 或 JMAP 定位高频分配点
运行时采集对象分配热点:
立即学习“Java免费学习笔记(深入)”;
- JFR(Java Flight Recorder) 开启 Object Allocation Sample 事件,可统计哪些方法/行号频繁 new 对象、对象大小分布
-
jmap -histo
查看当前堆中类实例数量与总占用,快速发现异常膨胀的类(如未关闭的流、缓存未清理) - VisualVM / JConsole / IntelliJ Memory View 结合堆转储(heap dump)分析对象引用链,确认生命周期是否合理
结合代码与 JVM 参数预判分配路径
对象分配行为受代码写法与 JVM 配置共同影响:
- 短生命周期小对象 → Eden 分配,多数在下一次 Young GC 回收
- 长期存活对象 → 经过多次 Minor GC 后晋升至 Old;若 Survivor 空间不足或对象年龄达阈值,立即晋升
- 逃逸分析开启(-XX:+DoEscapeAnalysis,默认开启)时,JIT 可能将栈上分配(标量替换),不进入堆 —— 此时 GC 日志不会体现该对象
- G1/ZGC/Shenandoah 等新 GC 器对分配行为建模更细,需关注其特定日志字段(如 G1 的 “Humongous allocation”)

















