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.pswp.cn/web/80470.shtml

          如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

          相关文章

          Ubuntu---omg又出bug了

          自用遇到问题的合集 250518——桌面文件突然消失 ANS&#xff1a;参考博文

          正则表达式与文本处理的艺术

          引言 在前端开发领域&#xff0c;文本处理是一项核心技能。正则表达式作为一种强大的模式匹配工具&#xff0c;能够帮助我们高效地处理各种复杂的文本操作任务。 正则表达式基础 什么是正则表达式&#xff1f; 正则表达式是一种用于匹配字符串中字符组合的模式。它由一系列…

          初学c语言15(字符和字符串函数)

          一.字符串分类函数 头文件&#xff1a;ctype.h 作用&#xff1a;判断是什么类型的字符 函数举例&#xff1a; 函数 符合条件就为真 islower判断是否为小写字符&#xff08;a~z&#xff09;isupper判断是否为大写字符&#xff08;A~Z&#xff09;isdigit十进制数字&#xf…

          12-串口外设

          一、串口外设的基本概述 1、基本定义 串口通信&#xff0c;通过在通信双方之间以比特位&#xff08;bit&#xff09;的形式逐一发送或接收数据&#xff0c;实现了信息的有效传递。其通信方式不仅简单可靠&#xff0c;而且成本很低。 2、stm32的串口 下面是两个MCU的数据交互&…

          NE555双音门铃实验

          1脚为地。通常被连接到电路共同接地。 2脚为触发输入端。 3脚为输出端&#xff0c;输出的电平状态受触发器的控制&#xff0c;而触发器受上比较器6脚和下比较器2脚的控制。当触发器接受上比较器A1从R脚输入的高电平时&#xff0c;触发器被置于复位状态&#xff0c;3脚输出低电…

          Redis分布式锁实现

          概述 为什么要要分布式锁 在并发编程中&#xff0c;我们通过锁&#xff0c;来避免由于竞争而造成的数据不一致问题。 通常&#xff0c;我们以synchronized 、Lock来使用它。Java中的锁&#xff0c;只能保证在同一个JVM进程内中执行 如果需要在分布式集群环境下的话&#xff0…

          软件设计师-错题笔记-网络基础知识

          1. 解析&#xff1a; 1.子网划分相关知识&#xff1a; 在IPv4地址中&#xff0c;/27表示子网掩码为255.255.255.224&#xff0c;它将一个C类网络&#xff08;默认子网掩码255.255.255.0&#xff09;进一步划分 对于子网掩码255.255.255.224&#xff0c;其对应的二进制为111…

          Fine-Tuning Llama2 with LoRA

          Fine-Tuning Llama2 with LoRA 1. What is LoRA?2. How does LoRA work?3. Applying LoRA to Llama2 models4. LoRA finetuning recipe in torchtune5. Trading off memory and model performance with LoRAModel ArgumentsReferences https://docs.pytorch.org/torchtune/ma…

          python打卡day29

          类的装饰器 知识点回顾 类的装饰器装饰器思想的进一步理解&#xff1a;外部修改、动态类方法的定义&#xff1a;内部定义和外部定义 回顾一下&#xff0c;函数的装饰器是 &#xff1a;接收一个函数&#xff0c;返回一个修改后的函数。类也有修饰器&#xff0c;类装饰器本质上确…

          十一、STM32入门学习之FREERTOS移植

          目录 一、FreeRTOS1、源码下载&#xff1a;2、解压源码 二、移植步骤一&#xff1a;在需要移植的项目中新建myFreeRTOS的文件夹&#xff0c;用于存放FREERTOS的相关源码步骤二&#xff1a;keil中包含相关文件夹和文件引用路径步骤三&#xff1a;修改FreeRTOSConfig.h文件的相关…

          2025 年十大网络安全预测

          随着我们逐步迈向 2026 年&#xff0c;网络安全领域正处于一个关键的转折点&#xff0c;技术创新与数字威胁以前所未有的复杂态势交织在一起。 地缘政治环境进一步加剧了这些网络安全挑战&#xff0c;国际犯罪组织利用先进的技术能力来追求战略目标。 人工智能在这一不断演变…

          Mac 环境下 JDK 版本切换全指南

          概要 在 macOS 上安装了多个 JDK 后&#xff0c;可以通过系统自带的 /usr/libexec/java_home 工具来查询并切换不同版本的 Java。只需在终端中执行 /usr/libexec/java_home -V 列出所有已安装的 JDK&#xff0c;然后将你想使用的版本路径赋值给环境变量 JAVA_HOME&#xff0c;…

          中级网络工程师知识点6

          1.堆叠方式可以共享使用交换机背板带宽&#xff1b;级联方式可以使用双绞线将交换机连接在一起 2.光功率计是专门测量光功率大小的仪器&#xff0c;在对光缆进行检测时&#xff0c;通过在光缆的发送端和接收端分别测量光功率&#xff0c;进而计算出光衰情况。 3.光时域反射计…

          动态规划——乌龟棋

          题目描述 解题思路 首先这是一个很明显的线性dp的题目&#xff0c;很容易发现规律 数据输入 我们用 h[ N ] 数组存储每一个格子的分数 用 cnt [ ]&#xff0c;数组表示每一中卡片的数目 1&#xff0c;状态表示 因为这里一个有4种跳跃方式可以选择 f[ i ][ a ][ b ][ c ][ d…

          C#自定义控件-实现了一个支持平移、缩放、双击重置的图像显示控件

          1. 控件概述 这是一个继承自 Control 的自定义控件&#xff0c;主要用于图像的显示和交互操作&#xff0c;具有以下核心功能&#xff1a; 图像显示与缩放&#xff08;支持鼠标滚轮缩放&#xff09;图像平移&#xff08;支持鼠标拖拽&#xff09;视图重置&#xff08;双击重置…

          C++ map multimap 容器:赋值、排序、大小与删除操作

          概述 map和multimap是C STL中的关联容器&#xff0c;它们存储的是键值对(key-value pairs)&#xff0c;并且会根据键(key)自动排序。两者的主要区别在于&#xff1a; map不允许重复的键multimap允许重复的键 本文将详细解析示例代码中涉及的map操作&#xff0c;包括赋值、排…

          AI Agent开发第70课-彻底消除RAG知识库幻觉(4)-解决知识库问答时语料“总重复”问题

          开篇 “解决知识库幻觉”系列还在继续,这是因为:如果只是个人玩玩,像自媒体那些说的什么2小时搭一个知识库+deepseek不要太香一类的RAG或者是基于知识库的应用肯定是没法用在企业级落地上的。 我们真的经历过或者正在经历的人都是知道的,怎么可能2小时就搭建完成一个知识…

          【DAY22】 复习日

          内容来自浙大疏锦行python打卡训练营 浙大疏锦行 仔细回顾一下之前21天的内容 作业&#xff1a; 自行学习参考如何使用kaggle平台&#xff0c;写下使用注意点&#xff0c;并对下述比赛提交代码 kaggle泰坦里克号人员生还预测

          【Docker】Docker Compose方式搭建分布式协调服务(Zookeeper)集群

          开发分布式应用时,往往需要高度可靠的分布式协调,Apache ZooKeeper 致力于开发和维护开源服务器&#xff0c;以实现高度可靠的分布式协调。具体内容见zookeeper官网。现代应用往往使用云原生技术进行搭建,如何用Docker搭建Zookeeper集群,这里介绍使用Docker Compose方式搭建分布…

          若依框架Consul微服务版本

          1、最近使用若依前后端分离框架改造为Consul微服务版本 在这里分享出来供大家参考 # Consul微服务配置参数已经放置/bin/Consul微服务配置目录 仓库地址&#xff1a; gitee&#xff1a;https://gitee.com/zlxls/Ruoyi-Consul-Cloud.git gitcode&#xff1a;https://gitcode.c…