当前位置: 首页 > news >正文

JVM如何处理多线程内存抢占问题

目录

1、堆内存结构

2、运行时数据

3、内存分配机制

3.1、堆内存结构

3.2、内存分配方式

1、指针碰撞

2、空闲列表

4、jvm内存抢占方案

4.1、TLAB

4.2、CAS

4.3、锁优化

4.4、逃逸分析与栈上分配

5、问题

5.1、内存分配竞争导致性能下降

5.2、伪共享(False Sharing)

1、对象内存结构

2、对象内存布局

3、问题表现

4、解决方案

5.3、内存泄漏(ThreadLocal 未清理)


前言:

        在多线程环境下,JVM 需要高效、安全地管理内存分配,避免多个线程同时竞争内存资源导致的性能下降或数据不一致问题。

如下图所示:

了解更多jvm的知识,可参考:关于对JVM的知识整理_谈谈你对jvm的理解-CSDN博客


1、堆内存结构

        由年前代和老年代组成。年轻代分为eden和survivor1和survivor2区。

        年轻代和老年代分别站别1/3和2/3。而eden区占比年轻代8/10,s1和s2分别占比1/10,1/10。

如下图所示:

        java堆里面存放的是数组和对象实例,字符串常量池、静态变量和TLAB。

如下图所示:

由上图可知:可以看到TLAB存储在堆中。

        TLAB 本身是存储在堆中,但它对每个线程都是独立的。一个线程在创建对象时会使用其自己的 TLAB 来进行分配,而不是直接访问共享的堆内存区域。

如下所示:


2、运行时数据

由下图所示:运行数据区由堆和方法区(元空间)组成。

        完整的执行过程由类加载系统、运行时数据区和执行引擎及本地方法库和接口组成。


3、内存分配机制

JVM 的内存分配主要发生在 堆(Heap) 上,涉及以下几个关键组件:

3.1、堆内存结构

  • 新生代(Young Generation):存放新创建的对象,分为 Eden区 和 Survivor区

  • 老年代(Old Generation):存放长期存活的对象。

  • TLAB(Thread-Local Allocation Buffer):每个线程私有的内存分配缓冲区。

3.2、内存分配方式

1、指针碰撞

如下图所示:

Bump-the-Pointer:适用于 连续内存空间(如 Serial、ParNew 等垃圾收集器)。

         通过原子操作移动指针分配内存。

2、空闲列表

如下图所示:

Free List:适用于 不连续内存空间(如 CMS、G1 等垃圾收集器)。

        维护一个空闲内存块列表,分配时查找合适的内存块。


4、jvm内存抢占方案

4.1、TLAB

全名(Thread-Local Allocation Buffer)。

1、作用

        每个线程在 Eden区 拥有一块私有内存(TLAB),用于分配小对象(默认约 1% Eden 大小)。避免多线程竞争全局堆内存指针,提升分配效率。

2、特点

TLAB 分配无需加锁,因为每个线程操作自己的缓冲区。

当 TLAB 用尽时,线程会申请新的 TLAB(可能触发锁竞争)。

-XX:+UseTLAB  # 默认启用
-XX:TLABSize=512k  # 调整 TLAB 大小

如下图所示:

4.2、CAS

(Compare-And-Swap)原子操作

适用场景

当 TLAB 不足或分配大对象时,线程需在 全局堆 分配内存。

JVM 使用 CAS(如 Atomic::cmpxchg 确保指针更新的原子性。

// HotSpot 源码中的内存分配逻辑(伪代码)
if (使用 TLAB) {从 TLAB 分配内存;
} else {do {old_value = 当前堆指针;new_value = old_value + 对象大小;} while (!CAS(&堆指针, old_value, new_value)); // 原子更新指针返回 old_value;
}

4.3、锁优化

如偏向锁、自旋锁

问题

    如果多个线程同时竞争全局堆内存,可能触发锁竞争。

    解决方案

    JVM 使用 偏向锁自旋锁 减少线程阻塞。

    例如,G1 垃圾收集器在分配时采用 分区(Region)锁,降低冲突概率。

    4.4、逃逸分析与栈上分配

    逃逸分析(Escape Analysis)

            JVM 分析对象是否可能被其他线程访问(即是否“逃逸”)。如果对象未逃逸,可直接在 栈上分配,避免堆内存竞争。

    如下图所示:

    启用方式

    -XX:+DoEscapeAnalysis  # 默认启用
    -XX:+EliminateAllocations  # 开启标量替换

    5、问题

            在上面介绍中,关于jvm如何可以解决内存抢占,下面解释下内存抢占引发的典型问题及解决方案。

    5.1、内存分配竞争导致性能下降

    表现

              多线程频繁分配对象时,new 操作变慢。

      解决方案

              增大 TLAB-XX:TLABSize)。使用对象池(如 Apache Commons Pool)。

      5.2、伪共享(False Sharing)

      1、对象内存结构

              在 Java 中,对象的所有实例字段(如 x 和 y)默认会连续存储在对象的内存布局中,减少内存碎片,一次性分配内存。

      代码示例:

      class FalseSharingExample {volatile long x; // 8字节volatile long y; // 8字节
      }
      • 对象头(Header):12 字节(64位 JVM,未压缩指针时)。

      • 字段 x:8 字节(紧接对象头)。

      • 字段 y:8 字节(紧接 x)。

      • 对齐填充(Padding):4 字节(见下文)。

      2、对象内存布局

              java对象的内存布局由对象头(12个字节)、实例数据、对象填充(8个字节)组成。

      如图所示:

      更多了解可参考Java对象的内存布局及GC回收年龄的研究-CSDN博客

      3、问题表现

              需要从 对象内存布局 和 CPU缓存行 两个角度分析。

      • x 和 y 的地址相差 8 字节(因为 long 类型占 8 字节)。

      • 它们必然位于同一缓存行(缓存行通常 64 字节)。

      代码示例:

      class FalseSharingExample {volatile long x; // 线程1修改volatile long y; // 线程2修改public static void main(String[] args) {FalseSharingExample example = new FalseSharingExample();Thread thread1 = new Thread(() -> {for (int i = 0; i < 1_0000_0000; i++) {example.x = i; // 高频修改x}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1_0000_0000; i++) {example.y = i; // 高频修改y}});long start = System.currentTimeMillis();thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("耗时: " + (System.currentTimeMillis() - start) + "ms");}
      }

                多个线程修改同一缓存行(Cache Line)的不同变量,导致 CPU 缓存频繁失效。

        运行结果

        • 由于 x 和 y 在同一缓存行,两个线程会互相使对方的缓存失效。

        • 耗时可能高达 5000ms(实际结果依赖CPU架构)。

        原因如下图所示:

        4、解决方案

        1、手动解决

        class ManualPaddingExample {volatile long x;// 填充56字节(64字节缓存行 - 8字节long)private long p1, p2, p3, p4, p5, p6, p7; volatile long y;public static void main(String[] args) { /* 同上 */ }
        }

        效果

        • x 和 y 被隔离到不同的缓存行。

        • 耗时可能降至 1000ms(性能提升5倍)。

        2、使用 @Contended 自动解决(Java 8+)

        @Contended 让 JVM 自动完成填充,代码更简洁:

        import sun.misc.Contended;class ContendedExample {@Contended  // 确保x独占缓存行volatile long x;@Contended  // 确保y独占缓存行volatile long y;public static void main(String[] args) { /* 同上 */ }
        }

        关键步骤

        1. 添加JVM参数(允许使用@Contended):

        -XX:-RestrictContended

        运行结果

                  耗时与手动填充一致(约 1000ms),但代码更干净。

          最终内存布局:

          | 对象头 (12字节) | x (8字节) | y (8字节) | 填充 (4字节) |
          |----------------|----------|----------|-------------|

          5.3、内存泄漏(ThreadLocal 未清理)

          • 表现

            • 线程池中 ThreadLocal 未调用 remove(),导致内存无法释放。

          • 解决方案

            • 必须 remove()

          try {threadLocal.set(value);// 业务逻辑
          } finally {threadLocal.remove(); // 清理
          }

          总结


          总结

          • TLAB 是 JVM 解决多线程内存竞争的核心机制,通过 线程私有缓冲区 减少锁竞争。

          • CAS 操作 用于全局堆内存分配,保证原子性。

          • 逃逸分析 和 栈上分配 可彻底避免堆内存竞争。

          • 伪共享 和 ThreadLocal 泄漏 需额外注意,通过缓存行填充和及时清理避免。

                  通过合理配置 JVM 参数(如 TLAB 大小)和优化代码(如使用对象池),可以显著降低多线程内存抢占的开销。

          http://www.xdnf.cn/news/513235.html

          相关文章:

        1. 【Java学习笔记】【第一阶段项目实践】房屋出租系统(面向对象版本)
        2. 【Linux】第十九章 管理SELinux安全性
        3. 数字格式化库 accounting.js的使用说明
        4. “Cloud Native English“云原生时代下的微服务架构设计:从理论到实战全解析
        5. 【数据结构】2-3-2 单链表的插入删除
        6. 结构型模式:代理模式
        7. 改进模糊C均值时序聚类+编码器状态识别!IPOA-FCM-Transformer组合模型
        8. 牛客网NC276055:三根木棒能否组成三角形问题详解(ACM中的A题)
        9. 【C++】尾置返回类型(Trailing Return Type)总结
        10. 多模态大语言模型arxiv论文略读(八十)
        11. vscode优化使用体验篇(快捷键)
        12. React 19版本refs也支持清理函数了。
        13. 【C++】set、map 容器的使用
        14. Java 中 == 与 equals() 详解
        15. 索引与数据结构、并行算法
        16. LlamaIndex中应用自定义提示词提升回答质量
        17. go语言协程调度器 GPM 模型
        18. 华为云Flexus+DeepSeek征文|基于华为云Flexus云服务的Dify 快速构建聊天助手
        19. 目标检测新突破:用MSBlock打造更强YOLOv8
        20. 如何使用WordPress创建美食博客
        21. 跨平台多用户环境下PDF表单“序列号生成的服务器端方案“
        22. 如何实现RTSP和RTMP低至100-200ms的延迟:直播SDK的技术突破
        23. Metasploit框架与网络安全攻防技术解析
        24. 标准库、HAl库和LL库(PC13初始化)
        25. 【甲方安全建设】Python 项目静态扫描工具 Bandit 安装使用详细教程
        26. 视差场(disparity field)
        27. Linux之基础IO
        28. MySQL 数据库备份与还原
        29. iOS APP启动页及广告页的实现
        30. 赋予AI更强的“思考”能力