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
          繁體地址,請注明出處:http://hk.pswp.cn/web/80470.shtml
          英文地址,請注明出處:http://en.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…