那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

JVM內存與K8s容器內存不一致引發的OOMKilled總結

馬哥Linux運維 ? 來源:稀土掘金技術社區 ? 作者:洛神灬殤 ? 2022-12-20 09:38 ? 次閱讀

在我們日常的工作當中,通常應用都會采用 Kubernetes 進行容器化部署,但是總是會出現一些問題,例如,JVM 堆小于 Docker 容器中設置的內存大小和 Kubernetes 的內存大小,但是還是會被 OOMKilled。在此我們介紹一下 K8s 的 OOMKilled 的 Exit Code 編碼。

Exit Code 137

表明容器收到了 SIGKILL 信號,進程被殺掉,對應 kill -9,引發 SIGKILL 的是 docker kill。這可以由用戶或由 docker 守護程序來發起,手動執行:docker kill

137 比較常見,如果 pod 中的 limit 資源設置較小,會運行內存不足導致 OOMKilled,此時 state 中的 ”OOMKilled” 值為 true,你可以在系統的 dmesg -T 中看到 OOM 日志。因為我的 heap 大小肯定是小于 Docker 容器以及 Pod 的大小的,為啥還是會出現 OOMKilled?

原因分析

這種問題常發生在 JDK8u131 或者 JDK9 版本之后所出現在容器中運行 JVM 的問題:在大多數情況下,JVM 將一般默認會采用宿主機 Node 節點的內存為 Native VM 空間(其中包含了堆空間、直接內存空間以及棧空間),而并非是是容器的空間為標準。

例如我的機器:

$dockerrun-m100MBopenjdk:8u121java-XshowSettings:vm-version
VMsettings:
Max.HeapSize(Estimated):444.50M
ErgonomicsMachineClass:server
UsingVM:OpenJDK64-BitServerVM

以上的信息出現了矛盾,我們在運行的時候將容器內存設置為 100MB,而 -XshowSettings:vm 打印出的 JVM 將最大堆大小為 444M,如果按照這個內存進行分配內存的話很可能會導致節點主機在某個時候殺死我的 JVM。

解決方案

JVM 感知 cgroup 限制

一種方法解決 JVM 內存超限的問題,這種方法可以讓 JVM 自動感知 docker 容器的 cgroup 限制,從而動態的調整堆內存大小。JDK8u131 在 JDK9 中有一個很好的特性,即 JVM 能夠檢測在 Docker 容器中運行時有多少內存可用。為了使 jvm 保留根據容器規范的內存,必須設置標志 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap。

注意:如果將這兩個標志與 Xms 和 Xmx 標志一起設置,那么 jvm 的行為將是什么?-Xmx 標志將覆蓋-XX:+ UseCGroupMemoryLimitForHeap 標志。

總結一下:

標志 -XX:+UseCGroupMemoryLimitForHeap 使 JVM 可以檢測容器中的最大堆大小。

-Xmx 標志將最大堆大小設置為固定大小。

除了 JVM 的堆空間,還會對于非堆和 jvm 的東西,還會有一些額外的內存使用情況。

使用 JDK9 的容器感知機制嘗試

$dockerrun-m100MBopenjdk:8u131java
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XshowSettings:vm-version
VMsettings:
Max.HeapSize(Estimated):44.50M
ErgonomicsMachineClass:server
UsingVM:OpenJDK64-BitServerVM

可以看出來通過內存感知之后,JVM 能夠檢測到容器只有 100MB,并將最大堆設置為 44M。我們調整一下內存大小看看是否可以實現動態化調整和感知內存分配,如下所示。

$dockerrun-m1GBopenjdk:8u131java
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XshowSettings:vm-version
VMsettings:
Max.HeapSize(Estimated):228.00M
ErgonomicsMachineClass:server
UsingVM:OpenJDK64-BitServerVM

我們設置了容器有 1GB 內存分配,而 JVM 使用 228M 作為最大堆。因為容器中除了 JVM 之外沒有其他進程在運行,所以我們還可以進一步擴大一下對于 Heap 堆的分配?

$dockerrun-m1GBopenjdk:8u131java
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=1-XshowSettings:vm-version
VMsettings:
Max.HeapSize(Estimated):910.50M
ErgonomicsMachineClass:server
UsingVM:OpenJDK64-BitServerVM

在較低的版本的時候可以使用 -XX:MaxRAMFraction 參數,它告訴 JVM 使用可用內存 /MaxRAMFract 作為最大堆。使用 -XX:MaxRAMFraction=1,我們將幾乎所有可用內存用作最大堆。從上面的結果可以看出來內存分配已經可以達到了 910.50M。

問題分析

最大堆占用總內存是否仍然會導致你的進程因為內存的其他部分(如“元空間”)而被殺死?答案:MaxRAMFraction=1 仍將為其他非堆內存留出一些空間。

但如果容器使用堆外內存,這可能會有風險,因為幾乎所有的容器內存都分配給了堆。您必須將-XX:MaxRAMFraction=2 設置為堆只使用 50% 的容器內存,或者使用 Xmx。

容器內部感知 CGroup 資源限制

Docker1.7 開始將容器 cgroup 信息掛載到容器中,所以應用可以從 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件獲取內存、 CPU 等設置,在容器的應用啟動命令中根據 Cgroup 配置正確的資源設置 -Xmx, -XX:ParallelGCThreads 等參數

在 Java10 中,改進了容器集成

Java10+ 廢除了 -XX:MaxRAM 參數,因為 JVM 將正確檢測該值。在 Java10 中,改進了容器集成。無需添加額外的標志,JVM 將使用 1/4 的容器內存用于堆。

java10+ 確實正確地識別了內存的 Docker 限制,但您可以使用新的標志 MaxRAMPercentage(例如:-XX:MaxRAMPercentage=75)而不是舊的 MaxRAMFraction,以便更精確地調整堆的大小,而不是其余的(堆棧、本機…)

java10+ 上的 UseContainerSupport 選項,而且是默認啟用的,不用設置。同時 UseCGroupMemoryLimitForHeap 這個就棄用了,不建議繼續使用,同時還可以通過 -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage 這些參數更加細膩的控制 JVM 使用的內存比率。

Java 程序在運行時會調用外部進程、申請 Native Memory 等,所以即使是在容器中運行 Java 程序,也得預留一些內存給系統的。所以 -XX:MaxRAMPercentage 不能配置得太大。當然仍然可以使用 -XX:MaxRAMFraction=1 選項來壓縮容器中的所有內存。

通過前面的講解我們知道了如何設置和控制 Java 應用對應的堆內存和容器內存的之間的關系,進而防止 JVM 的堆內存超過了容器內存,避免容器出現 OOMKilled 的情況。但是在整個 JVM 進程體系而言,不僅僅只包含了 Heap 堆內存,其實還有其他相關的內存存儲空間是需要我們考慮的,一邊防止這些內存空間會造成我們的容器內存溢出的場景,正如下圖所示。

f2c4ec2c-7f9d-11ed-8abf-dac502259ad0.png

接下來我們需要進行分析出 heap 之外的一部分就是對外內存就是 Off Heap Space,也就是 Direct buffer memory 堆外內存。主要通過的方式就是采用 Unsafe 方式進行申請內存,大多數場景也會通過 Direct ByteBuffer 方式進行獲取。好廢話不多說進入正題。

JVM 參數 MaxDirectMemorySize

我們先研究一下 jvm 的 -XX:MaxDirectMemorySize,該參數指定了 DirectByteBuffer 能分配的空間的限額,如果沒有顯示指定這個參數啟動 jvm,默認值是 xmx 對應的值(低版本是減去幸存區的大小)。

DirectByteBuffer 對象是一種典型的”冰山對象”,在堆中存在少量的泄露的對象,但其下面連接用堆外內存,這種情況容易造成內存的大量使用而得不到釋放

-XX:MaxDirectMemorySize

-XX:MaxDirectMemorySize=size 用于設置 New I/O (java.nio) direct-buffer allocations 的最大大小,size 的單位可以使用 k/K、m/M、g/G;如果沒有設置該參數則默認值為 0,意味著 JVM 自己自動給 NIO direct-buffer allocations 選擇最大大小。

-XX:MaxDirectMemorySize 的默認值是什么?

在 sun.misc.VM 中,它是 Runtime.getRuntime.maxMemory(),這就是使用-Xmx 配置的內容。而對應的 JVM 參數如何傳遞給 JVM 底層的呢?主要通過的是 hotspot/share/prims/jvm.cpp。我們來看一下 jvm.cpp 的 JVM 源碼來分一下。

//Convertthe-XX:MaxDirectMemorySize=commandlineflag
//tothesun.nio.MaxDirectMemorySizeproperty.
//Dothisaftersettinguserpropertiestopreventpeople
//fromsettingthevaluewitha-Doption,asrequested.
//Leaveemptyifnotsupplied
if(!FLAG_IS_DEFAULT(MaxDirectMemorySize)){
charas_chars[256];
jio_snprintf(as_chars,sizeof(as_chars),JULONG_FORMAT,MaxDirectMemorySize);
Handlekey_str=java_lang_String::create_from_platform_dependent_str("sun.nio.MaxDirectMemorySize",CHECK_NULL);
Handlevalue_str=java_lang_String::create_from_platform_dependent_str(as_chars,CHECK_NULL);
result_h->obj_at_put(ndx*2,key_str());
result_h->obj_at_put(ndx*2+1,value_str());
ndx++;
}

jvm.cpp 里頭有一段代碼用于把 -XX:MaxDirectMemorySize 命令參數轉換為 key 為 sun.nio.MaxDirectMemorySize 的屬性。我們可以看出來他轉換為了該屬性之后,進行設置和初始化直接內存的配置。針對于直接內存的核心類就在www.docjar.com/html/api/su…[1]

publicclassVM{

//theinitlevelwhentheVMisfullyinitialized
privatestaticfinalintJAVA_LANG_SYSTEM_INITED=1;
privatestaticfinalintMODULE_SYSTEM_INITED=2;
privatestaticfinalintSYSTEM_LOADER_INITIALIZING=3;
privatestaticfinalintSYSTEM_BOOTED=4;
privatestaticfinalintSYSTEM_SHUTDOWN=5;


//0,1,2,...
privatestaticvolatileintinitLevel;
privatestaticfinalObjectlock=newObject();

//......

//Auser-settableupperlimitonthemaximumamountofallocatabledirect
//buffermemory.ThisvaluemaybechangedduringVMinitializationif
//"java"islaunchedwith"-XX:MaxDirectMemorySize=".
//
//Theinitialvalueofthisfieldisarbitrary;duringJREinitialization
//itwillberesettothevaluespecifiedonthecommandline,ifany,
//otherwisetoRuntime.getRuntime().maxMemory().
//
privatestaticlongdirectMemory=64*1024*1024;

上面可以看出來 64MB 最初是任意設置的。在 -XX:MaxDirectMemorySize 是用來配置 NIO direct memory 上限用的 VM 參數。可以看一下 JVM 的這行代碼。

product(intx,MaxDirectMemorySize,-1,
"MaximumtotalsizeofNIOdirect-bufferallocations")

但如果不配置它的話,direct memory 默認最多能申請多少內存呢?這個參數默認值是-1,顯然不是一個“有效值”。所以真正的默認值肯定是從別的地方來的。

//Returnsthemaximumamountofallocatabledirectbuffermemory.
//ThedirectMemoryvariableisinitializedduringsysteminitialization
//inthesaveAndRemovePropertiesmethod.
//
publicstaticlongmaxDirectMemory(){
returndirectMemory;
}

//......

//Saveaprivatecopyofthesystempropertiesandremove
//thesystempropertiesthatarenotintendedforpublicaccess.
//
//Thismethodcanonlybeinvokedduringsysteminitialization.
publicstaticvoidsaveProperties(Mapprops){
if(initLevel()!=0)
thrownewIllegalStateException("Wronginitlevel");

//onlymainthreadisrunningatthistime,sosavedPropsand
//itscontentwillbecorrectlypublishedtothreadsstartedlater
if(savedProps==null){
savedProps=props;
}

//Setthemaximumamountofdirectmemory.Thisvalueiscontrolled
//bythevmoption-XX:MaxDirectMemorySize=.
//Themaximumamountofallocatabledirectbuffermemory(inbytes)
//fromthesystempropertysun.nio.MaxDirectMemorySizesetbytheVM.
//Ifnotsetorsetto-1,themaxmemorywillbeused
//Thesystempropertywillberemoved.
Strings=props.get("sun.nio.MaxDirectMemorySize");
if(s==null||s.isEmpty()||s.equals("-1")){
//-XX:MaxDirectMemorySizenotgiven,takedefault
directMemory=Runtime.getRuntime().maxMemory();
}else{
longl=Long.parseLong(s);
if(l>-1)
directMemory=l;
}
//Checkifdirectbuffersshouldbepagealigned
s=props.get("sun.nio.PageAlignDirectMemory");
if("true".equals(s))
pageAlignDirectMemory=true;
}
//......
}

從上面的源碼可以讀取 sun.nio.MaxDirectMemorySize 屬性,如果為 null 或者是空或者是 - 1,那么則設置為 Runtime.getRuntime().maxMemory();如果有設置 MaxDirectMemorySize 且值大于 -1,那么使用該值作為 directMemory 的值;而 VM 的 maxDirectMemory 方法則返回的是 directMemory 的值。

因為當 MaxDirectMemorySize 參數沒被顯式設置時它的值就是 -1,在 Java 類庫初始化時 maxDirectMemory() 被 java.lang.System 的靜態構造器調用,走的路徑就是這條:

if(s.equals("-1")){
//-XX:MaxDirectMemorySizenotgiven,takedefault
directMemory=Runtime.getRuntime().maxMemory();
}

而 Runtime.maxMemory() 在 HotSpot VM 里的實現是:

JVM_ENTRY_NO_ENV(jlong,JVM_MaxMemory(void))
JVMWrapper("JVM_MaxMemory");
size_tn=Universe::heap()->max_capacity();
returnconvert_size_t_to_jlong(n);
JVM_END

這個 max_capacity() 實際返回的是 -Xmx 減去一個 survivor space 的預留大小。

結論分析說明:

MaxDirectMemorySize 沒顯式配置的時候,NIO direct memory 可申請的空間的上限就是 -Xmx 減去一個 survivor space 的預留大小。例如如果您不配置 -XX:MaxDirectMemorySize 并配置 -Xmx5g,則 "默認" MaxDirectMemorySize 也將是 5GB-survivor space 區,并且應用程序的總堆+直接內存使用量可能會增長到 5 + 5 = 10 Gb。

其他獲取 maxDirectMemory 的值的 API 方法

BufferPoolMXBean 及 JavaNioAccess.BufferPool (通過 SharedSecrets 獲取) 的 getMemoryUsed 可以獲取 direct memory 的大小;其中 java9 模塊化之后,SharedSecrets 從原來的 sun.misc.SharedSecrets 變更到 java.base 模塊下的 jdk.internal.access.SharedSecrets;要使用 --add-exports java.base/jdk.internal.access=ALL-UNNAMED 將其導出到 UNNAMED,這樣才可以運行:

publicBufferPoolMXBeangetDirectBufferPoolMBean(){
returnManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
.stream()
.filter(e->e.getName().equals("direct"))
.findFirst()
.orElseThrow();
}
publicJavaNioAccess.BufferPoolgetNioBufferPool(){
returnSharedSecrets.getJavaNioAccess().getDirectBufferPool();
}

內存分析問題

-XX:+DisableExplicitGC 與 NIO 的 direct memory 用了 -XX:+DisableExplicitGC 參數后,System.gc() 的調用就會變成一個空調用,完全不會觸發任何 GC(但是“函數調用”本身的開銷還是存在的哦~)。做 ygc 的時候會將新生代里的不可達的 DirectByteBuffer 對象及其堆外內存回收了,但是無法對 old 里的 DirectByteBuffer 對象及其堆外內存進行回收,這也是我們通常碰到的最大的問題,如果有大量的 DirectByteBuffer 對象移到了 old,但是又一直沒有做 cms gc 或者 full gc,而只進行 ygc,那么我們的物理內存可能被慢慢耗光,但是我們還不知道發生了什么,因為 heap 明明剩余的內存還很多 (前提是我們禁用了 System.gc)。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內存
    +關注

    關注

    8

    文章

    3055

    瀏覽量

    74336
  • 容器
    +關注

    關注

    0

    文章

    499

    瀏覽量

    22124
  • JVM
    JVM
    +關注

    關注

    0

    文章

    158

    瀏覽量

    12261

原文標題:JVM 內存與 K8s 容器內存不一致引發的 OOMKilled 總結

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    STM32H750DMA+SPi內存數據不一致的原因?

    發現用stm32H750 dma+spi讀寫數據時發現當spi速度大于8M時出現內存不一致的問題,我再讀之前已經用SCB_CleanInvalidateDCache_by_Addr這個函數無效化
    發表于 05-24 07:53

    不一致IP設置

    有沒有人使用cRIO之后,遇到過這個問題?我使用Ni MAX連接的時候,系統狀態直是:不一致IP設置請教大家,這個問題該怎么處理?
    發表于 06-25 08:49

    K8S容器編排的互通測試

    K8S容器編排之NetWorkPolicy官方實例
    發表于 06-06 11:28

    鋰離子電池組一致性的含義與不一致性的改進措施

    深入分析,并總結了生產、配組、使用、維護等過程提出彌補不一致性的措施。 不一致性 鋰離子電池一致性是指用于成組的單體電池的初期性能指標的一致
    發表于 10-15 13:15 ?12次下載
    鋰離子電池組<b class='flag-5'>一致</b>性的含義與<b class='flag-5'>不一致</b>性的改進措施

    基于偏好不一致熵的偏好決策方法

    針對多規則有序決策系統中的偏好決策問題,根據有序決策的偏好不一致特性,提出了種基于偏好不一致熵的偏好決策方法。首先,定義了樣本的偏好不一致熵( PIEO),用來度量特定樣本相對于樣本
    發表于 12-05 11:50 ?0次下載

    不一致數據上精確決策樹生成算法

    近年來,隨著現實生活中數據量的不斷增大,不一致數據的出現也越發頻繁,這使得人工修正不一致數據變得更加耗時.而且。人工修正數據方法本身也存在著不可避免的人為操作錯誤,因此。這種修正方法不再可行.如何不
    發表于 12-26 16:13 ?0次下載
    <b class='flag-5'>不一致</b>數據上精確決策樹生成算法

    感興趣區域不一致性決策算法

    醫學影像感興趣區域( ROI)的噪聲和疾病誤判是個典型的不一致性決策問題,同時也是困擾臨床診斷的個難題。針對這個問題,基于宏觀與微觀結合、全局與局部相結合的思想,提出了基于一致度、
    發表于 01-02 18:43 ?0次下載

    分布式大數據不一致性檢測

    關系數據庫中可能存在數據不一致性現象,關系數據庫數據質量的個主要問題是存在違反函數依賴情況,為找出不一致數據需要進行函數依賴沖突檢測.集中式數據庫中可以通過SQL技術檢測不一致情況,
    發表于 01-12 16:29 ?0次下載

    鋰電池組不一致性的原因及危害是怎樣的

    鋰電池組不一致性的原因及損害,看了就明白!鋰電池組電壓不一致會發生什么損害?怎么應對鋰電池組不一致性的損害?鋰電池參數的不一致首要是指容量、內阻、開路電壓的
    發表于 03-17 17:39 ?1.2w次閱讀

    鋰電池組不一致性的原因是什么,它的危害有哪些

    鋰電池組不一致性的原因及損害,看了就明白!鋰電池組電壓不一致會發生什么損害?怎么應對鋰電池組不一致性的損害?鋰電池參數的不一致首要是指容量、內阻、開路電壓的
    發表于 03-17 17:41 ?4569次閱讀

    k8s是什么意思?kubeadm部署k8s集群(k8s部署)|PetaExpres

    k8s是什么意思? kubernetes簡稱K8s,是個開源的,用于管理云平臺中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器
    發表于 07-19 13:14 ?1153次閱讀

    跑大模型AI的K8s與普通K8s的區別分析

    Kubernetes是個在大量節點上管理容器的系統,其主要功能總結起來,就是在想要啟動容器的時候,負責“找個「空閑」節點,啟動
    發表于 09-03 12:07 ?983次閱讀

    什么是電芯的不一致性?電芯不一致會造成什么后果?

    什么是電芯的不一致性?電芯不一致會造成什么后果? 電芯是電池組成部分之,由正負極、電解質和隔膜組成。在電動車和移動設備中廣泛使用的鋰離子電池,通常由數十個甚至數百個電芯組成。電芯的不一致
    的頭像 發表于 11-06 10:56 ?3698次閱讀

    什么是鋰離子電池不一致性?如何提高鋰離子電池的一致性?

    什么是鋰離子電池不一致性?鋰離子電池不穩定的原因?如何提高鋰離子電池的一致性? 鋰離子電池不一致性是指同批次或不同批次的鋰離子電池在性能上出現不一
    的頭像 發表于 11-10 14:49 ?2127次閱讀

    充放電不一致影響超級電容器性能的原因及解決方案

    充放電不一致影響超級電容器性能的原因及解決方案? 充放電不一致是指超級電容器在充電和放電過程中無法保持一致的電壓和電流特征。這種
    的頭像 發表于 02-03 15:02 ?2206次閱讀
    二八杠玩法| 澳门百家乐限红规则| 马牌百家乐官网现金网| 游艇会百家乐的玩法技巧和规则| 网上百家乐官网娱乐场开户注册| 乐众国际| 迪威百家乐赌场娱乐网规则| 盐城百家乐官网的玩法技巧和规则 | 海王星开户| 大中华百家乐的玩法技巧和规则 | 百家乐视频下载| 百家乐官网趋势图怎么看| 德州扑克桌| 至尊百家乐赌场娱乐网规则| A8百家乐官网的玩法技巧和规则 | 百家乐犯法| 模拟百家乐官网的玩法技巧和规则 | 连环百家乐官网怎么玩| 通道| 百家乐小九梭哈| 基础百家乐官网博牌规| 百家乐官网巴黎| 大发888娱乐场东南网| 沙龙百家乐娱乐城| 百家乐官网赌场视频| 百家乐官网娱乐城优惠| 大发888下载17| 百家乐庄闲的几率| 百家乐网娱乐城| 郑州百家乐官网高手| 大庆市| 百家乐乐百家娱乐场| 百家乐游戏怎么刷钱| 百家乐官网走势图研究| 金宝博备用网址| 水果机器| 百家乐诀| 百家乐官网桌出租| 百家乐咨询网址| 百家乐试玩1000元| 菲彩百家乐官网的玩法技巧和规则|