Docker和JVM應用OOM那些事

1. 前言

Java 應用運行過程中你是否遇到以下類似問題

  1. 為什么 Java 應用所在的 Docker 容器內(nèi)存使用量不會減少?
  2. 發(fā)生 OOM 后程序還能運行嗎?
  3. Java 應用所在的容器為什么宕機或者自動重啟了?

在回答以上問題前,我們先了解下“OOM”和“JVM 內(nèi)存管理”。本文涉及的 JVM 相關描述特指 HotSpot JDK8。

2. OOM 機制

2.1. Linux 的 OOM 機制

當系統(tǒng)內(nèi)存不足時,Linux 內(nèi)核會觸發(fā) OOM Killer(OOM 殺手)機制。OOM Killer 會嘗試找出最適合終止的進程,并向其發(fā)送 SIGKILL 信號,使其被強制終止。選擇目標進程的策略通?;谶M程的 OOM 分數(shù)(OOM Score),該分數(shù)反映了進程使用內(nèi)存的情況和重要性。 具體選擇哪個進程殺掉,有一套算分策略,分兩部分:

  1. 參考進程占用的內(nèi)存情況打分,進程內(nèi)存開銷是變化的,因此該值也會動態(tài)變化;
  2. 用戶可以設置 oom_score_adj 參數(shù),取值范圍是[-1000,1000],oom_score_adj 的值越小,進程得分越少,也就越難被殺掉。

2.2. Docker 限制內(nèi)存的原理

  1. Docker 基于 Linux 內(nèi)核提供的 cgroups 功能,可以限制容器在運行時使用到的資源,比如內(nèi)存、CPU 等。容器的內(nèi)存隨容器內(nèi)進程內(nèi)存使用量的增加而超過了設置的上限,在啟用 OOM killer 時(默認啟用),就會導致容器觸發(fā) linux 的 OOM 機制而被終止進程。
  2. Docker 可在啟動容器時使用–oom-kill-disable=true 來禁止被 OOM 殺掉,默認啟用(一般不建議禁用)。docker 的這個參數(shù)對應 cgroups 的 memory.oom_control 參數(shù)。如果開啟,進程如果嘗試申請內(nèi)存超過允許,就會被系統(tǒng) OOM killer 終止。OOM killer 在每個使用 cgroup 內(nèi)存子系統(tǒng)中都是默認開啟的。如果 OOM killer 關閉,那么進程嘗試申請的內(nèi)存超過允許,那么它就會被暫停,直到額外的內(nèi)存被釋放。
  3. 運行在 Docker 中的 Java 應用是一個進程,自然而然會受 Linux 內(nèi)核的 OOM 機制影響。

小知識:k8s 對資源的限制也是通過 cgroups 來實現(xiàn)的,POD 本身并沒有限制資源的能力。

2.3. JVM 的 OOM

  1. 官方說明,當 JVM 沒有足夠的內(nèi)存來為對象分配空間并且垃圾回收器也已經(jīng)沒有空間可回收時,就會拋出這個 Error。這個錯誤不是普通的異常,已經(jīng)嚴重到無法被應用處理。
  2. 發(fā)生 OutOfMemoryError(OOM)錯誤可能會導致 JVM 退出。當 JVM 的內(nèi)存不足以分配新的對象時,會拋出 OOM 錯誤。這通常是由于程序使用了過多的內(nèi)存或存在內(nèi)存泄漏導致的。在發(fā)生 OOM 錯誤后,JVM 可能會嘗試進行一些垃圾回收操作來釋放內(nèi)存,但如果沒有足夠的可用內(nèi)存,JVM 可能無法繼續(xù)正常執(zhí)行,并最終退出。

2.3.1. JVM 內(nèi)存管理機制簡述

JVM 就像一個需要一些內(nèi)存才能運行的虛擬操作系統(tǒng),而從操作系統(tǒng)請求分配內(nèi)存是一項耗時的操作。當 JVM 中的程序任務執(zhí)行完成時,雖然 GC 回收器可能回收了這部分內(nèi)存(邏輯上釋放),但大多數(shù) JVM 不會將內(nèi)存釋放回操作系統(tǒng),僅僅是釋放回 JVM 中對應的內(nèi)存區(qū)域;等到下次執(zhí)行任務時,無需再從底層操作系統(tǒng)請求內(nèi)存資源,JVM 的這種架構有助于提高性能。

JVM 中 MemoryUsage 各指標的含義

  1. init,JVM 啟動時向操作系統(tǒng)申請的初始內(nèi)存量。
  2. used,當前使用的內(nèi)存量。
  3. committed,保證供虛擬機使用的內(nèi)存量,這部分內(nèi)存量可能隨時間而變化(增加或減少),committed 的部分一直是大于或等于 used 的內(nèi)存量。
  4. max,JVM 內(nèi)存管理中可以被使用的內(nèi)存上限。

2.3.2. JVM 運行時數(shù)據(jù)區(qū)域

  1. 常見的圖


    jmm-general.png
  2. 整理的圖

    Docker&JVM運行時內(nèi)存區(qū)域關系.png

2.3.3. 內(nèi)存溢出區(qū)域

2.3.3.1. 堆

最容易遇到內(nèi)存溢出的區(qū)域。

  1. 異常

      java.lang.OutOfMemoryError: Java heap space
    
  2. 處理方法

    • 一般在事前配置好 JVM 堆溢出的自動導堆轉(zhuǎn)儲快照的參數(shù)。
        -XX:+HeapDumpOnOutOfMemoryError
        -XX:HeapDumpPath=/opt/xx/logs/heapdump.hprof
      
    • 使用 JProfiler 或者 MAT 分析堆轉(zhuǎn)儲快照;分清楚是內(nèi)存泄露還是內(nèi)存溢出。
      • 內(nèi)存泄露,通過工具進一步查看泄露對象到 GC Roots 的引用鏈,一般可以比較準確定位到具體的代碼。
      • 非內(nèi)存泄露,檢查 JVM 的堆參數(shù)(-Xmx 和-Xms)配置是否合理。

2.3.3.2. 虛擬機棧和本地方法棧

HotSpot 虛擬機中并不區(qū)分虛擬機棧和本地方法棧,棧容量由-Xss 參數(shù)設定,JDK8 中默認值為 1M。

  1. 異常

      // 異常1:棧溢出
      java.lang.StackOverflowError
    
      // 異常2:服務器剩余內(nèi)存不足
      java.lang.OutOfMemoryError: unable to create native thread
    
  2. 處理方法

    • 線程請求的棧深度大于 JVM 所允許的最大深度,檢查是否-Xss 設置過小導致或者程序問題。
    • 棧幀內(nèi)存無法分配(線程大小*N>=總內(nèi)存-堆-元空間-其它內(nèi)存占用),檢查 JVM 參數(shù)配置是否合理或者程序問題導致線程過多。

2.3.3.3. 方法區(qū)

  1. 異常

      java.lang.OutOfMemoryError: Metaspace
    
  2. 處理方法

    • JVM 參數(shù)配置不合理,-XX:MaxMetaspaceSize 設置過小。
    • 程序問題,運行時生成大量動態(tài)類,比如使用了 CGLib 字節(jié)碼增強、用到了動態(tài)語言(如 Groovy 等)、大量 JSP 或動態(tài)產(chǎn)生 JSP 文件應用(JSP 第一次運行時需要編譯為 Java 類)、基于 OSGi 的應用(即使同一個類文件,被不同的加載器加載也會視為不同的類)等。

2.3.3.4. Compressed class space

JVM 有個功能是 CompressedOops ,目的是為了在 64bit 機器上使用 32bit 的原始對象指針(oop,ordinary object pointer,這里直接就當成指針概念理解就可以)來節(jié)約成本(減少內(nèi)存/帶寬使用),提高性能(提高 Cache 命中率)。

使用了這個壓縮功能,每個對象中的 Klass* 字段就會被壓縮成 32bit(不是所有的 oop 都會被壓縮的), Klass* 指向的 Klass 在永久代(Java7 及之前)。但是在 Java8 及之后,永久代沒了,有了一個 Metaspace,于是之前壓縮指針 Klass* 指向的這塊 Klass 區(qū)域有了一個名字 —— Compressed Class Space。Compressed Class Space 是 Metaspace 的一部分,默認大小為 1G。所以其實 Compressed Class Space 這個名字取得很誤導,壓縮的并不是 Klass,而是 Klass*。

  1. JDK8 中,啟用對象和類指針壓縮(默認啟用)且堆內(nèi)存-Xmx<32G,會額外分配的非堆空間;可通過參數(shù)-XX:CompressedClassSpaceSize 控制大小,在啟動的時候就限制 Class Space 的大小,默認值是 1G,啟動后不可以修改。它是 reserved 不是 committed 的內(nèi)存。
  2. 禁用指針壓縮(-XX:-UseCompressedOops)或者堆內(nèi)存-Xmx>=32G 時,此區(qū)域在 Metaspace 中,不獨立存在。
  3. 以下異常描述特指第一種獨立分配空間時的情況。
  1. 異常

      java.lang.OutOfMemoryError: Compressed class space
    
  2. 處理方法

    • 一般是 JVM 參數(shù)設置不合理,可通過-XX:CompressedClassSpaceSize 控制。
    • 如果是程序問題做進一步排查優(yōu)化。

2.3.3.5. Code Cache

  1. JIT 編譯成本地機器碼的緩存區(qū)域大小,不同 jdk 版本和機器默認值不同(一般是 240m),可由-XX:ReservedCodeCacheSize=240m 控制大小。
  2. 關聯(lián)參數(shù)-XX:+UseCodeCacheFlushing,代碼緩存區(qū)即將耗盡,嘗試回收一些早期編譯、很久未被調(diào)用的方法,默認打開。
  1. 異常

     Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled.
     Java HotSpot(TM) 64-Bit Server VM warning: Try increasing the code cache size using -XX:ReservedCodeCacheSize=
    
  2. 處理方法

    • 通過參數(shù)-XX:ReservedCodeCacheSize 設置合理的值。

2.3.3.6. 直接內(nèi)存

直接內(nèi)存(Direct Memory)的容量大小可以通過-XX:MaxDirectMemorySize 參數(shù)來指定,如果不指定,默認與 Java 堆最大值(由-Xmx 指定)一致。

  1. 異常

       // 異常1
       java.lang.OutOfMemoryError: Direct buffer memory
       // 異常2
       java.lang.OutOfMemoryError
         at sum.misc.Unsafe.allocateMemory(Native Method)
    
  2. 處理方法

    • 直接內(nèi)存導致的內(nèi)存溢出,一個明顯特征是在 Heap Dump 文件中不會看見明顯異常,如果程序中直接或者間接使用了 DirectMemory(典型間接使用就是 NIO),可以考慮重點檢查直接內(nèi)存方面的原因;
    • 可能的原因是 JVM 參數(shù)配置不合理(比如-XX:MaxDirectMemorySize 設置不合理),或者程序問題。
  3. 注意事項

    • 如果使用 Java 自帶的 ByteBuffer.allocateDirect(size) 或者直接 new DirectByteBuffer(capacity) , 這樣受-XX:MaxDirectMemorySize 這個 JVM 參數(shù)的限制。其實底層都是用的 Unsafe#allocateMemory,區(qū)別是對大小做了限制. 如果超出限制直接 OOM。
    • 如果通過反射的方式拿到 Unsafe 的實例,然后用 Unsafe 的 allocateMemory 方法分配堆外內(nèi)存。確實不受-XX:MaxDirectMemorySize 這個 JVM 參數(shù)的限制 。所限制的內(nèi)存大小為操作系統(tǒng)的內(nèi)存。
    • 如果不設置-XX:MaxDirectMemorySize 默認的話,是跟堆內(nèi)存大小保持一致。 [堆內(nèi)存大小如果不設置的話,默認為操作系統(tǒng)的 1/4, 所以 DirectMemory 的大小限制 JVM 的 Runtime.getRuntime().maxMemory()內(nèi)存大小 ]

3. 問題分析

在了解了 Linux 的 OOM 機制和 JVM 內(nèi)存管理的基本知識后,前面的 3 個問題的分析就變簡單了。

3.1. 為什么 Java 應用所在的 Docker 容器內(nèi)存使用量不會減少?

由上文中“JVM 內(nèi)存管理機制簡述”我們可以直接得到答案,減少的是 JVM 中管理的內(nèi)存,占用的操作系統(tǒng)內(nèi)存(docker 內(nèi)存)一般情況下不會減少。

  1. 早期,運維/工程人員老問 XX 應用的 Docker 內(nèi)存占用超過 80%了并且沒有回落,趕緊檢查下程序是不是有問題。
  2. 多數(shù)情況下點開 JVM 的內(nèi)存監(jiān)控面板,發(fā)現(xiàn)只是某段業(yè)務繁忙時刻 JVM used 的內(nèi)存升高,一定時間后又回落到正常水平,只是這個時候 committed 的內(nèi)存大部分情況下并不會釋放回給操作系統(tǒng)導致 Docker 內(nèi)存長期處于高位。

因此,Docker 的內(nèi)存占用并不能很好反應 JVM 真實的內(nèi)存使用情況,推薦大家看應用的內(nèi)存占用時一定要結合 JVM 的內(nèi)存監(jiān)控來看。那么有沒有辦法歸還空余內(nèi)存給操作系統(tǒng)呢?

JVM 提供了-XX:MinHeapFreeRatio 和-XX:MaxHeapFreeRatio 兩個參數(shù),用于配置這個歸還策略。

  • MinHeapFreeRatio 代表當空閑區(qū)域大小下降到該值時,會進行擴容,擴容的上限為 Xmx。
  • MaxHeapFreeRatio 代表當空閑區(qū)域超過該值時,會進行“縮容”,縮容的下限為 Xms。
    JVM 在歸還的時候,是線性遞增歸還的,并不是一次全部歸還。這個歸還內(nèi)存的機制,在不同的垃圾回收器,甚至不同的 JDK 版本不一。以下表格摘自網(wǎng)友實測:
JAVA 版本 GC 回收器 VM Options 是否可以“歸還”
JAVA 8 UseParallelGC(ParallerGC + ParallerOld) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40
JAVA 8 CMS+ParNew -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 是,需要 4 次 FGC 之后觸發(fā)
JAVA 8 UseG1GC(G1) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 -XX:+UseG1GC 是,首次 FGC 之后觸發(fā)
JAVA 11 UseG1GC(G1) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 是,首次 FGC 之后觸發(fā)
JAVA 16 UseZGC(ZGC) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 -XX:+UseZGC

其它:

  • JAVA 9 后-XX:-ShrinkHeapInSteps 參數(shù)(默認啟用),在第一次 FGC 后,可以讓 JVM 以非線性遞增的方式歸還內(nèi)存。如果禁用,會立即將堆減小到目標大?。ㄊ?MaxHeapFreeRatio 限制),禁用此選項可能會遇到性能下降問題。
  • JAVA 12 后的 ShenandoahGC(openJDK 特有),不需要 FGC 就能異步回收不再使用的內(nèi)存并歸還給操作系統(tǒng)。
  • JAVA 12 后新增兩個 G1 參數(shù) G1PeriodicGCInterval( milliseconds ) 及 G1PeriodicGCSystemLoadThreshold,設置為 0 的話,表示禁用,可以設置定期自動觸發(fā) GC 操作,從而達到歸還內(nèi)存的目的,啟用定期 GC 可能會遇到性能下降問題。

3.2. 發(fā)生 OOM 后程序還能運行嗎?

可能處于運行中,也可能退出。 以堆內(nèi)存溢出作說明:

  1. 線程棧空間是線程獨享,OOM 后線程被 kill,線程棧上的空間被釋放。
  2. 堆空間是共享的,存在兩種情況:
    1. 被 Kill 掉的線程中的對象可能被該線程之外的其他線程引用,這部分被引用的對象就沒有辦法被 GC 掉,其他線程如果此時需要申請資源但是又資源又不足,那么此時其他線程就不能運行,現(xiàn)象就是系統(tǒng)會卡住,然后極端情況引起連鎖反應,外部請求持續(xù)進入,積壓的線程過多,此時是有可能觸發(fā) Linux 的 OOM。
    2. 被 Kill 掉的線程中的對象未被其它線程應用,這部分空間也能被釋放掉,此時程序可以正常運行。
  3. JVM 的其它內(nèi)存區(qū)域道理類似;當然,發(fā)生 OOM 之后可能導致應用狀態(tài)不一致,建議最好重啟。以下是幾種自動退出運行狀態(tài)的方式:
    • -XX:OnOutOfMemoryError(推薦),發(fā)生 OOM 時,JVM 就會調(diào)用此參數(shù)設置的腳本,此種方式可以對 JVM 進行優(yōu)雅的重啟應用。示例:
         -XX:OnOutOfMemoryError=/scripts/restart-myapp.sh
      
    • -XX:+CrashOnOutOfMemoryError,發(fā)生 OOM 時,配置此參數(shù)會導致 JVM 立即退出(非優(yōu)雅退出),并生成包含崩潰信息的文本,這些崩潰信息大多很基本,不足以對 OOM 進行故障排除(經(jīng)測驗,此參數(shù)不會影響自動導堆操作,會在導完堆轉(zhuǎn)儲之后才退出)。輸出消息示例如下:
      Aborting due to java.lang.OutOfMemoryError: GC overhead limit exceeded
      #
      # A fatal error has been detected by the Java Runtime Environment:
      #
      #  Internal Error (debug.cpp:308), pid=26064, tid=0x0000000000004f4c
      #  fatal error: OutOfMemory encountered: GC overhead limit exceeded
      #
      # JRE version: Java(TM) SE Runtime Environment (8.0_181-b13) (build 1.8.0_181-b13)
      # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode windows-amd64 compressed oops)
      # Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
      #
      # An error report file with more information is saved as:
      # C:workspacetier1app-svntrunkbuggyapphs_err_pid26064.log
      #
      # If you would like to submit a bug report, please visit:
      #   http:
      #
      
    • -XX:+ExitOnOutOfMemoryError,大體同上一個參數(shù),只是少了崩潰消息的輸出。

3.3. Java 應用所在的容器為什么宕機或者自動重啟了?

被 Linux OOM-killer 殺掉了進程。

3.3.1. JVM max 內(nèi)存量 < 容器內(nèi)存上限,并且 JVM max 內(nèi)存量 < 操作系統(tǒng)可用內(nèi)存

此種情況一般不會被 Linux 的 OOM-killer 殺掉進程。

  1. 對應 JVM 會溢出的區(qū)域報錯,此處不贅述。
  2. 由上個問題可以得出 JVM 一般情況還處于運行狀態(tài),往往不會導致 Docker 停止或重啟,最可能發(fā)生的情況是 Java 應用程序卡頓。

3.3.2. JVM committed 內(nèi)存量 < 容器內(nèi)存上限,并且 JVM committed 內(nèi)存量 > 操作系統(tǒng)可用內(nèi)存

被 Linux OOM-killer 殺掉了進程。

  • 應用容器被終止,docker inspect <容器>

      "State": {
           "Status": "exited",
           "Running": false,
           "Paused": false,
           "Restarting": false,
           "OOMKilled": false, //此處為false
           "Dead": false,
           "Pid": 0,
           "ExitCode": 137,
           "Error": "",
           "StartedAt": "2023-06-17T07:55:15.1271987Z",
           "FinishedAt": "2023-06-17T07:55:34.9597495Z"
       }
    
  • 系統(tǒng)日志輸出 OOM 信息:

     > 執(zhí)行dmesg -T 或者 egrep -i -r 'killed process' /var/log
    
     [Mon Jun 26 13:44:10 2023] Out of memory: Kill process 26727 (java) score 275 or sacrifice child
     [Mon Jun 26 13:44:10 2023] Killed process 26727 (java), UID 0, total-vm:14137304kB, anon-rss:8854784kB, file-rss:0kB, shmem-rss:0kB
    
    

3.3.3. JVM committed 內(nèi)存量 > 容器內(nèi)存上限,并且 JVM committed 內(nèi)存量 < 操作系統(tǒng)可用內(nèi)存

被 Linux OOM-killer 殺掉了進程。

  • 系統(tǒng)日志報錯如下:

     > dmesg -T|grep "out of memory"
    
     [Sat Jun 17 15:55:34 2023] Memory cgroup out of memory: Killed process 4906 (java) total-vm:4563472kB, anon-rss:119968kB, file-rss:16776kB, shmem-rss:0kB, UID:0 pgtables:1036kB oom_score_adj:0
    
  • 查看 docker inspect <容器>

      "State": {
           "Status": "exited",
           "Running": false,
           "Paused": false,
           "Restarting": false,
           "OOMKilled": true, //此處為true,代表內(nèi)存超過容器上限被主動kill。
           "Dead": false,
           "Pid": 0,
           "ExitCode": 137,
           "Error": "",
           "StartedAt": "2023-06-17T07:55:15.1271987Z",
           "FinishedAt": "2023-06-17T07:55:34.9597495Z"
       }
    

3.3.4. 那么如何合理規(guī)劃內(nèi)存?

JVM 參數(shù)調(diào)優(yōu)是一個循序漸進的過程,很難做到一蹴而就。以下以常見的 SpringBoot 應用內(nèi)存分配為例,假設需要配置 2C4G 堆內(nèi)存:

  1. 堆內(nèi)存 4G,-Xms4G -Xmx4G,一般為了性能,防止 JVM 頻繁申請內(nèi)存,最大和最小堆內(nèi)存會設置成一樣。
  2. Metaspace 256m, -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256M ,此區(qū)域和引用的 Jar、加載的類數(shù)量等有關。
  3. CompressedClassSpace 64m,-XX:CompressedClassSpaceSize=一般建議設置為 MaxMetaspaceSize 的 20% 左右 256*0.2=52m 左右。
  4. ??臻g,512m,2C4G 經(jīng)驗值并發(fā)數(shù)在 400,考慮還有一些后臺線程,按 512 個預估,jdk8 之后的??臻g默認值為 1m,則??臻g總計占用至少 512m。
  5. Code Cache 128m,-XX:ReservedCodeCacheSize=128m,默認值為 240m,在自己程序穩(wěn)定運行一段時間后,觀察下這塊區(qū)域的大小,進一步設置合理值。
  6. GC 400m,具體占用大小和實際堆內(nèi)存大小以及 GC 回收器有關,從幾十兆到幾百兆不等。Parallel GC 不會占什么內(nèi)存,G1 最多會占的內(nèi)存大小為堆內(nèi)存 10% 左右,ZGC 會最多會占內(nèi)存大小為堆內(nèi)存的 15~20% 左右額外內(nèi)存,這塊內(nèi)存比較不好估算,結合監(jiān)控持續(xù)調(diào)優(yōu)。
  7. Direct Memory 64m,-XX:MaxDirectMemorySize=64m,看是否用到 NIO 相關特性,結合監(jiān)控進行調(diào)優(yōu)。
    總計需要設置的 Docker 內(nèi)存上限為 5.5G 左右=4096+256+64+512+128+400+64=5520m

4. 總結

  1. OOM 并不是 JVM 獨有,Linux 下也有 OOM 機制;需要區(qū)分好運維反饋的 OOM 是哪一種。
  2. 合理設置操作系統(tǒng)中各 Docker 實例的內(nèi)存上限是前提,在此前提下才能合理設置好 JVM 相關內(nèi)存分配參數(shù)。
  3. JVM 運行時涉及的內(nèi)存區(qū)域,不僅僅是堆、元空間,還有文中截圖中涉及的區(qū)域,都需要做好內(nèi)存的合理分配。

5. 其它你可能需要知道的事

  1. -XX:MaxMetaspaceSize,必須配置,默認基本是無窮大,但仍然受本地內(nèi)存大小的限制。
  2. -XX:MaxDirectMemorySize,程序用到直接內(nèi)存,比如 NIO 特性時,務必設置合理數(shù)值,默認 64m,達到上限會觸發(fā) Full GC。
  3. JDK8 默認的 GC 收集器是 Parallel Scavenge +Serial Old(PS MarkSweep),針對 Web 應用場景,建議改用 CMS 或者 G1。
    • Parallel Scanvenge,新生代多線程收集器,適合在后臺運算而不需要太多交互的分析任務,會導致 STW。
    • Serial Old,老年代單線程收集器,適合客戶端模式下使用,會導致 STW。
  4. 常用的 GC 收集器,ParNew+CMS、G1 也是會導致 STW,只是 STW 的階段不同、時長不同。(ZGC、ShenandoahGC 也一樣)
  5. 使用 Docker 運行 JVM 時,最好禁用 swap,當用到 swap 內(nèi)存時容易導致應用性能下降??稍谶\行容器實例時通過制定--memory-swap 等于-m 設置的內(nèi)存大小來規(guī)避 或者 Linux 禁用 swap。

6. 參考資料

  1. OOP-Klass
  2. Compressed Class Space
  3. TLAB
  4. HotSopt 虛擬機的內(nèi)存管理
  5. docker 內(nèi)存 limit 與 swap 限制
  6. 理解 OutOfMemoryError
  7. 深入理解堆外內(nèi)存
  8. JVM 內(nèi)存不釋放
  9. 5 大 GC 的內(nèi)存伸縮能力
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容