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. **栈上分配**:对未逃逸对象优化,彻底避免同步。

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

相关推荐
恶语伤人六月寒1 小时前
深⼊理解 JVM 执⾏引擎
jvm
Java知识库4 小时前
Java BIO、NIO、AIO、Netty面试题(已整理全套PDF版本)
java·开发语言·jvm·面试·程序员
Geek__19924 小时前
Sqlite3交叉编译全过程
jvm·数据库·sqlite
碎梦归途6 小时前
23种设计模式-结构型模式之代理模式(Java版本)
java·开发语言·jvm·设计模式·代理模式
{⌐■_■}9 小时前
【go】什么是Go语言中的GC,作用是什么?调优,sync.Pool优化,逃逸分析演示
java·开发语言·javascript·jvm·数据库·后端·golang
碎梦归途9 小时前
23种设计模式-创建型模式之建造者模式(Java版本)
java·开发语言·jvm·设计模式·intellij-idea·建造者模式
化作晚霞15 小时前
JVM有什么调优参数?
java·开发语言·jvm
OO好久不见OO1 天前
IDEA使用jclasslib Bytecode Viewer查看jvm字节码
java·jvm·intellij-idea
Code成立1 天前
第3章 垃圾收集器与内存分配策略《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
jvm