一、内存分配策略
Java 对象的内存分配主要发生在堆内存(新生代、老年代),少数情况可能分配在栈(栈上分配)或直接内存(堆外内存)。核心分配策略包括:
-
优先分配到新生代 Eden 区 绝大多数新创建的对象首先分配在新生代的 Eden 区。当 Eden 区空间不足时,触发 Minor GC,存活对象会被移至 Survivor 区(From 区)。
-
大对象直接进入老年代 大对象(如长字符串、大数组)会直接分配到老年代,避免在新生代频繁复制(可通过
-XX:PretenureSizeThreshold配置阈值)。 -
长期存活对象进入老年代 每个对象有年龄计数器,每经历一次 Minor GC 存活则年龄 + 1,当年龄达到阈值(默认 15,
-XX:MaxTenuringThreshold)时进入老年代。 -
动态年龄判定Survivor 区中相同年龄的对象总和超过 Survivor 空间的一半时,年龄≥该年龄的对象直接进入老年代(无需等到阈值)。
-
空间分配担保Minor GC 前,JVM 检查老年代最大可用连续空间是否大于新生代所有对象总大小:
- 若成立,Minor GC 安全;
- 若不成立但开启
-XX:-HandlePromotionFailure,检查老年代可用空间是否大于历次晋升老年代的平均大小,若成立则尝试 Minor GC(有风险),否则触发 Full GC。
-
栈上分配(逃逸分析) 通过逃逸分析,若对象未逃逸出方法,则直接分配在栈上,无需进入堆,减少 GC 压力(需开启
-XX:+DoEscapeAnalysis)。 -
标量替换 对未逃逸的对象,将其拆解为基本类型变量分配在栈上(需开启
-XX:+EliminateAllocations)。
二、垃圾回收(GC)策略
GC 的核心是回收堆中不再被引用的对象,根据回收区域分为:
1. Minor GC(新生代 GC)
- 触发条件:Eden 区空间不足时触发。
- 回收区域:新生代(Eden + Survivor)。
- 回收算法:复制算法(效率高,新生代对象存活率低)。
- 过程:将 Eden 和 From Survivor 存活对象复制到 To Survivor,清空 Eden 和 From Survivor,然后交换 From/To Survivor 角色。
- 特点:频率高、速度快,暂停时间短。
2. Major GC / Full GC(老年代 GC)
-
Major GC :仅回收老年代的 GC(多与 Minor GC 并发发生);Full GC:回收新生代 + 老年代 + 元空间的 GC(Stop-The-World 时间长)。
-
触发条件:
- 老年代空间不足(如大对象分配失败、Minor GC 后晋升老年代的对象超过老年代剩余空间);
- 元空间(Metaspace)不足(永久代时代为永久代满);
- 调用
System.gc()(建议性,JVM 可能忽略); - CMS GC 时出现 "Concurrent Mode Failure"(并发回收时老年代空间不足);
- 空间分配担保失败。
-
回收算法:老年代多采用标记 - 清除 / 标记 - 整理算法(对象存活率高,复制算法效率低)。
-
特点:频率低、速度慢,暂停时间长(CMS 等并发 GC 可减少暂停)。
三、常见垃圾收集器的回收策略
- Serial GC:新生代 Serial(复制)+ 老年代 Serial Old(标记 - 整理),单线程,适合单核心场景。
- ParNew GC:新生代并行版(多线程复制)+ 老年代 Serial Old,配合 CMS 使用。
- Parallel Scavenge:新生代并行复制,关注吞吐量(吞吐量 = 用户时间 /(用户时间 + GC 时间)),老年代 Parallel Old(标记 - 整理)。
- CMS(Concurrent Mark Sweep):老年代并发标记 - 清除,低延迟,分初始标记、并发标记、重新标记、并发清除阶段(会产生内存碎片)。
- G1(Garbage-First):分区堆内存,混合回收新生代和老年代,兼顾吞吐量和延迟,优先回收垃圾多的区域。
总结
- 内存分配:优先 Eden 区,大对象 / 长期对象进老年代,栈上分配优化。
- Minor GC:新生代高频回收,速度快;
- Full GC:回收全堆,成本高,需尽量避免(如优化对象生命周期、调整 JVM 参数)。
合理配置新生代 / 老年代比例(如 -XX:NewRatio)、GC 算法和阈值,可有效减少 Full GC 频率,提升性能。