JVM学习

1,JVM调优的目的?

减少STW,也即是减少GC次数,提高程序运行效率。

2,为什么要设计一个STW机制?

这和垃圾回收有关(GC),确保同一时刻系统状态的一致性,防止并发修改导致的不一致性。

确保操作的一致性
  • 场景 :在垃圾回收(GC)、内存压缩或某些系统级操作中,需要冻结整个系统的状态,防止并发修改导致的数据不一致

  • 示例:如果垃圾回收器在标记存活对象时,用户线程同时修改对象引用,可能导致错误回收仍在使用的内存(即"悬挂指针")。

  • STW的作用:暂停所有线程后,系统可以安全地遍历对象图或执行原子操作,避免中间状态干扰。

3,JVM内存分配策略
1),对象内存分配策略

JVM 主要采用 分代分配(Generational Allocation) ,优先在**新生代(Young Generation)**分配,具体策略如下:

  1. 优先在 Eden 区分配

    • 大多数新对象在 Eden 区 分配。

    • 如果 Eden 区空间不足,触发 Minor GC(Young GC)。

  2. 大对象直接进入老年代

    • 如果对象大小超过 -XX:PretenureSizeThreshold(默认 0,由 GC 决定),直接进入 老年代(Old Generation)

    • 避免大对象在 Eden 区频繁复制(如大数组)。

  3. 长期存活对象进入老年代

    • 对象每经历一次 Minor GC,年龄(Age) +1。

    • 当年龄达到 -XX:MaxTenuringThreshold(默认 15),晋升到老年代。

  4. 空间分配担保(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. 内存分配时的线程安全挑战**

当多个线程同时创建对象时,可能引发以下问题:

  1. **内存指针竞争**:多个线程同时移动指针(Bump the Pointer),导致数据覆盖。

  2. **空闲列表冲突**:多个线程同时修改空闲列表(Free List),导致内存分配错误。

  3. **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. 优化建议**

  1. **减少对象分配频率**:
  • 重用对象(如对象池)。

  • 避免在热点代码中频繁创建小对象。

  1. **调整 TLAB 大小**:
  • 如果小对象分配竞争激烈,可增大 TLAB(`-XX:TLABSize`)。
  1. **选择合适的 GC**:
  • 低延迟场景用 ZGC/Shenandoah,高吞吐用 G1/Parallel GC。

**总结**

JVM 通过 **TLAB(线程本地缓冲)** + **CAS/锁同步** 的组合策略,在对象内存分配时保证线程安全:

  1. **TLAB 优先**:无锁分配,减少竞争。

  2. **CAS 回退**:TLAB 不足时,使用原子操作更新全局指针。

  3. **锁同步**:某些 GC 使用锁管理空闲列表。

  4. **栈上分配**:对未逃逸对象优化,彻底避免同步。

这样既保证了多线程安全,又尽可能减少了锁竞争对性能的影响。

相关推荐
掘金-我是哪吒11 分钟前
分布式微服务系统架构第158集:JavaPlus技术文档平台日更-JVM基础知识
jvm·分布式·微服务·架构·系统架构
abigalexy1 小时前
深入JVM底层-内存分配算法
jvm
weixin_ab13 小时前
JMM--数据原子操作
jvm
超级小忍15 小时前
JVM 中的垃圾回收算法及垃圾回收器详解
java·jvm
喝可乐的布偶猫20 小时前
Java类变量(静态变量)
java·开发语言·jvm
abigalexy1 天前
深入JVM底层-垃圾回收GC算法
jvm
麦兜*2 天前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
真实的菜2 天前
JVM类加载系统详解:深入理解Java类的生命周期
java·开发语言·jvm
在未来等你2 天前
JVM调优实战 Day 15:云原生环境下的JVM配置
java·jvm·性能优化·虚拟机·调优
黄雪超3 天前
JVM——函数式语法糖:如何使用Function、Stream来编写函数式程序?
java·开发语言·jvm