jvm内存分配与回收策略通过对象创建行为、启动参数和运行时表现共同体现:小对象入eden,大对象直入老年代,gc日志记录分代回收细节,引用类型提供可编程回收控制。

JVM 的内存分配与回收策略不是靠代码“写出来”的,而是通过对象创建行为、JVM 启动参数和运行时表现共同体现的。你写的每一行 new,都在触发底层分配逻辑;而 GC 日志、堆内存变化、甚至 OOM 异常,就是这些策略在代码执行过程中的真实反馈。
对象创建直接反映分配规则
每次调用 new 创建对象,JVM 就按既定策略决定它去哪儿:
- 小对象(如普通 POJO、数组)默认进入新生代 Eden 区;
- 大对象(比如超过 -XX:PretenureSizeThreshold 设置的数组)可能直接进老年代;
- 频繁新建又快速丢弃的对象,在 Eden 满时触发 Minor GC,存活对象经 Survivor 区年龄计数后晋升老年代。
例如这段代码:
byte[] a = new byte[2 * 1024 * 1024]; // 2MB → Eden<br>byte[] b = new byte[6 * 1024 * 1024]; // 6MB → 可能直接进老年代(若开启并满足阈值)
配合 -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M 运行,日志中会明确出现 Eden: 8192K->0K 或 ParNew: ... (to space) 等字样,这就是分配策略在运行时的直接证据。
GC 日志是策略最直观的“输出”
加了 -XX:+PrintGCDetails 和 -Xloggc:gc.log 后,每次 GC 都会记录下:
- 哪一代被回收(ParNew 表示新生代,CMS / G1 Mixed 表示老年代或混合收集);
- 回收前后的空间占用(如
eden space 8192K, from space 1024K, to space 1024K); - 是否发生担保失败(
Allocation Failure导致 Full GC),说明 Survivor 容量不足或老年代碎片化。
这些不是调试信息,而是 JVM 正在严格执行分代回收、空间分配担保等策略的实时快照。
主动干预能验证策略边界
你可以用代码刻意“试探”策略的临界点:
- 连续分配多个大数组,观察是否跳过 Eden 直接进老年代(验证
-XX:PretenureSizeThreshold); - 在循环中反复创建短生命周期对象,再置为
null,看 Minor GC 频率和 Survivor 区对象年龄增长(验证“长期存活晋升”规则); - 用
System.gc()触发建议式回收(不保证执行),对比加与不加-XX:+DisableExplicitGC的日志差异。
注意:System.gc() 本身不体现分配策略,但它能帮你观察老年代回收时机,间接验证对象何时真正“变老”。
引用类型让回收行为可编程
四种引用直接影响对象何时被回收,这是唯一能在 Java 代码里“写出来”的回收控制:
-
强引用:普通
Object obj = new Object(),只要引用链存在,GC 绝不回收; -
软引用:
SoftReference<byte> ref = new SoftReference(new byte[5*1024*1024]);</byte>,内存紧张时才回收; -
弱引用:
WeakReference<string> w = new WeakReference(new String("hello"));</string>,下一次 GC 就可能消失; -
虚引用:仅用于跟踪对象被回收的时刻,必须配合
ReferenceQueue使用。
它们不改变分配位置,但改变了“对象是否算垃圾”的判定逻辑——这是 JVM 回收策略在应用层的可编程接口。











