容器中Java 程序OOMKilled緣由淺析

背景:

業務的容器化剛剛搞完,線上開始告警,容器重啓,容器重啓。describe pod 查看緣由是OOMKilledjava

分析:

OOMKilled 是pod 中的進程使用的內存超過了.spec.containers[*].resources.limits.memory中定義的內存限制,在超出限制後, kubernetes 會向容器中的進程(pid=1)發送kill -9 信號。kill -9 信號對於進程來講是不可捕捉的,進程沒法在收到-9 信號後優雅的退出。 這對於業務來講是有損的。那麼爲啥進程會超過容器的limit 限制呢?
查看容器中進程的啓動參數:linux

java -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai -XX:MetaspaceSize=128m -jar bxr-web-1.0.jar

查看容器的limit限制web

k8s-master-01#kubectl get pods -n calculation bxr-web-dd656458b-8m4fb -o=custom-columns=name:.metadata.name,namespace:.metadata.namespace,memory-limit:.spec.containers[0].resources.limits.memory
name                      namespace     memory-limit
bxr-web-dd656458b-8m4fb   calculation   2000Mi

進程沒有設置內存限制,可是這個業務以前在虛擬機上運行時,配置相同,啓動參數也是如此,爲何上線到容器中會常常出現OOMKilled 的狀況呢。這裏就須要說到docker對進程資源的限制。docker

docker 經過 cgroup 來控制容器使用的資源配額,包括 CPU、內存、磁盤三大方面,基本覆蓋了常見的資源配額和使用量控制。可是在java 的早期版本中(小於1.8.131),不支持讀取cgroup的限制。 默認是從/proc/目錄讀取可用內存。可是容器中的/proc目錄默認是掛載的宿主機的內存目錄。即java 讀取的到可用的內存是宿主機的內存。那麼天然會致使進程超出容器limit 限制的問題。
驗證:spa

起初, 咱們採用爲進程設置-Xmx參數來限制進程的最大heap(堆)內存。例如。 容器的limit限制爲3G。 那麼設置java進程的最大堆內存爲2.8G,採用這種方式後,容器重啓的狀況少了不少,但仍是偶爾會出現OOMKilled 的狀況。由於-xms 只能設置java進程的堆內存。 可是其餘非堆內存的佔用一旦超過預留的內存。仍是會被kubernetes kil掉。附java 內存結構:
image線程

JVM內存結構主要有三大塊:堆內存、方法區和棧code

堆內存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內存又被分紅三部分,Eden空間、From Survivor空間、To Survivor空間,默認狀況下年輕代按照8:1:1的比例來分配;blog

方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,爲與Java堆區分,方法區還有一個別名Non-Heap(非堆);進程

棧又分爲java虛擬機棧和本地方法棧主要用於方法的執行。內存

那麼有沒有辦法能讓java 正確識別容器的內存限制呢?這裏有三種方法:

  1. 升級java版本。Java 10支持開箱即用的容器,它將查找linux cgroup信息。這容許JVM基於容器限制進行垃圾收集。默認狀況下使用標誌打開它。
-XX:+UseContainerSupport

值得慶幸的是,其中一些功能已被移植到8u131和9之後。可使用如下標誌打開它們。

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
  1. LXCFS,FUSE filesystem for LXC是一個常駐服務,它啓動之後會在指定目錄中自行維護與上面列出的/proc目錄中的文件同名的文件,容器從lxcfs維護的/proc文件中讀取數據時,獲得的是容器的狀態數據,而不是整個宿主機的狀態。 這樣。java進程讀取到的就是容器的limit 限制。而不是宿主機內存
  2. -XX:MaxRAM=`cat /sys/fs/cgroup/memory/memory.limit_in_bytes` 經過MaxRAM 參數讀取默認的limit限制做爲java 內存的最大可用內存。同時結合-Xmx 設置堆內存大小
相關文章
相關標籤/搜索