1,JVM调优的目的?
减少STW,也即是减少GC次数,提高程序运行效率。
2,为什么要设计一个STW机制?
这和垃圾回收有关(GC),确保同一时刻系统状态的一致性,防止并发修改导致的不一致性。
确保操作的一致性
-
场景 :在垃圾回收(GC)、内存压缩或某些系统级操作中,需要冻结整个系统的状态,防止并发修改导致的数据不一致。
-
示例:如果垃圾回收器在标记存活对象时,用户线程同时修改对象引用,可能导致错误回收仍在使用的内存(即"悬挂指针")。
-
STW的作用:暂停所有线程后,系统可以安全地遍历对象图或执行原子操作,避免中间状态干扰。
3,JVM内存分配策略
1),对象内存分配策略
JVM 主要采用 分代分配(Generational Allocation) ,优先在**新生代(Young Generation)**分配,具体策略如下:
-
优先在 Eden 区分配
-
大多数新对象在 Eden 区 分配。
-
如果 Eden 区空间不足,触发 Minor GC(Young GC)。
-
-
大对象直接进入老年代
-
如果对象大小超过
-XX:PretenureSizeThreshold
(默认 0,由 GC 决定),直接进入 老年代(Old Generation)。 -
避免大对象在 Eden 区频繁复制(如大数组)。
-
-
长期存活对象进入老年代
-
对象每经历一次 Minor GC,年龄(Age) +1。
-
当年龄达到
-XX:MaxTenuringThreshold
(默认 15),晋升到老年代。
-
-
空间分配担保(Handle Promotion)
-
如果 Minor GC 后 Survivor 区放不下存活对象,JVM 会检查老年代剩余空间:
-
如果足够,直接进入老年代。
-
如果不足,触发 Full GC。
-
-
2),对象动态年龄判断机制
在 Survivor 区(From/To)中,对象的年龄通常由 MaxTenuringThreshold
控制(默认 15)。但 JVM 还采用 动态年龄计算(Dynamic Ageing) 来优化晋升策略:
规则
-
目标 :让 Survivor 区中年龄 1 + 年龄 2 + ... + 年龄 N 的对象总大小 > 50% Survivor 区,则所有年龄 ≥ N 的对象直接晋升老年代。
-
作用:
-
避免 Survivor 区被长期存活的小对象占满。
-
减少不必要的复制开销。
-
总结
|----------|-------------------------------------------------|
| 对象创建 | 类加载 → 内存分配(指针碰撞/空闲列表)→ 初始化 → 对象头设置 → 构造方法执行 |
| 内存分配 | Eden 优先 → 大对象直接进老年代 → 长期存活晋升老年代 → 空间担保 |
| 动态年龄 | Survivor 区中年龄 1+2+...+N 的对象 >50% 时,年龄 ≥N 的直接晋升 |
在 JVM 中,**对象内存分配** 是一个高频操作,而多线程环境下,多个线程可能同时申请内存,因此必须保证分配的**线程安全**。JVM 主要通过以下机制确保对象内存分配的线程安全:
**1. 内存分配时的线程安全挑战**
当多个线程同时创建对象时,可能引发以下问题:
-
**内存指针竞争**:多个线程同时移动指针(Bump the Pointer),导致数据覆盖。
-
**空闲列表冲突**:多个线程同时修改空闲列表(Free List),导致内存分配错误。
-
**TLAB 分配失败**:线程本地缓冲区(TLAB)用尽后,回退到全局分配时的竞争。
4, JVM 的内存分配线程安全机制
JVM 采用多种策略来保证对象内存分配的线程安全:
(1) TLAB(Thread-Local Allocation Buffer)线程本地分配缓冲区
-
**原理 **:
-
每个线程在 **Eden 区** 预先分配一小块私有内存(TLAB),线程优先在自己的 TLAB 中分配对象。
-
分配时仅需移动线程本地的指针,无需全局同步。
-
**优点**:
-
**无锁分配**,减少竞争。
-
适合小对象的高频分配。
-
**缺点**:
-
TLAB 用尽后,仍需全局分配(可能触发同步)。
-
**相关参数**:
-
`-XX:+UseTLAB`(默认开启)
-
`-XX:TLABSize`(调整 TLAB 大小)
(2) CAS(Compare-And-Swap)分配
-
**适用场景 **:
-
当 TLAB 不足或分配大对象时,回退到 **Eden 区的全局分配**。
-
使用 **CAS(原子操作)** 更新全局指针(Bump the Pointer)。
-
**优点**:
-
比锁更轻量级。
-
**缺点**:
-
CAS 失败时需要重试,可能影响性能。
(3) 锁同步(如 Mutex)
-
**适用场景**:
-
某些垃圾回收器(如 CMS)使用 **空闲列表(Free List)** 管理内存,分配时需加锁。
-
**实现方式**:
-
通过 **pthread_mutex** 或 **JVM 内部锁** 保护空闲列表。
-
**缺点**:
-
锁竞争可能成为瓶颈。
**(4) 逃逸分析与栈上分配**
-
**原理**:
-
如果 JVM 通过 **逃逸分析(Escape Analysis)** 发现对象**不会逃逸出线程**(即仅被当前线程使用),则直接在 **栈上分配**。
-
栈是线程私有的,无需同步。
-
**优点**:
-
完全无锁,减少 GC 压力。
-
**限制**:
-
仅适用于**未逃逸的小对象**(默认不开启,需 `-XX:+DoEscapeAnalysis`)。
**3. 不同垃圾回收器的分配策略**
| **GC 收集器** | **内存分配方式** | **线程安全机制** |
|---------------------|------------------------------|-------------------------------|
| **Serial / ParNew** | 指针碰撞(Bump the Pointer) | TLAB + CAS(全局分配) |
| **CMS** | 空闲列表(Free List) | TLAB + 锁(Free List 修改) |
| **G1 / ZGC** | 分区(Region)分配 | TLAB + CAS(全局分配) |
**4. 优化建议**
- **减少对象分配频率**:
-
重用对象(如对象池)。
-
避免在热点代码中频繁创建小对象。
- **调整 TLAB 大小**:
- 如果小对象分配竞争激烈,可增大 TLAB(`-XX:TLABSize`)。
- **选择合适的 GC**:
- 低延迟场景用 ZGC/Shenandoah,高吞吐用 G1/Parallel GC。
**总结**
JVM 通过 **TLAB(线程本地缓冲)** + **CAS/锁同步** 的组合策略,在对象内存分配时保证线程安全:
-
**TLAB 优先**:无锁分配,减少竞争。
-
**CAS 回退**:TLAB 不足时,使用原子操作更新全局指针。
-
**锁同步**:某些 GC 使用锁管理空闲列表。
-
**栈上分配**:对未逃逸对象优化,彻底避免同步。
这样既保证了多线程安全,又尽可能减少了锁竞争对性能的影响。