Docker內JVM參數的簡單學習
背景
公司內部有K8S的項目.
基於K8S內容器的JVM參數的設置與標準虛擬機運行不太一樣.
產品內部的啓動腳本有一個設置, 在內存大於16G的情況下
默認取內存總量的四分之一作爲堆區.
在虛擬化時 這樣處理其實是沒有問題的, 但是容器內運行可能存在問題.
Docker 運行能夠看到宿主機所有的內存,
這樣計算可能會超過Docker容器的資源限制
會導致被OOM-killer 需要注意 OOM-killer與 JVM的out of memory 是不一樣的.
基於此, 最近幾天研究了下容器內的JVM參數設置, 簡要總結如下
關於JVM針對容器的支持情況
JDK1.8 是在191update 裏面添加了對容器運行的識別與優化.
前期的版本可以使用參數進行開啓, 但是因爲我們不用這麼低版本的JDK
這裏只是簡單記錄一下的版本的參數:
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=2
知識來源:
https://zhuanlan.zhihu.com/p/120168070?utm_source=wechat_session
需要注意 JVM使用的內存有:
總內存 = Heap + Code Cache + Metaspace + Symbol tables + Other JVM structures +
Thread stacks + Direct buffers + Mapped files + Native Libraries + Malloc overhead + ...
關於JVM針對容器的支持情況
高於JDK1.8u191版本的JDK就會好很多
有幾個浮點類型的參數可以使用
-XX:{Initial|Min|Max}RAMPercentage
使用者幾個參數之後會根據容器運行的資源限制自動進行堆區的劃分.
注意有資料說(還是上一個知乎連接), 這裏必須使用浮點類型的比如60.0和80.0
這樣就會默認將 limits的限制的 60% 作爲堆區.
其他內存用作棧區, codecache 方法區 源數據庫 本地內存等.
注意 如果limits 限制比較小, 建議還是不能大部分用於堆區的,
避免非堆區出現OOM或者是棧溢出.
關於JVM內存佔用的情況
在非容器的-Xms,-Xmx等參數以及-XX:{Initial|Min|Max}RAMPercentage的參數
按照阿里和Oracle JVM官方文檔說明, 建議初始化/最小/最大都設置成一樣的大小
避免因爲JVM 堆區的收縮導致STW的時間增加. 影響客戶體驗.
但是雖然有最小和初始化的參數, 但是查看內存的resident memory 時發現啓動時以及
啓動後的一段時間 內存是與設置的數據不一樣的.
其實核心問題是JVM爲了啓動的性能, 不會將申請的虛擬化內存直接進行initial處理.
只有用到時纔會進行向操作系統申請具體的物理內存. 此時纔會有具體的resident memory的變化.
需要注意可以使用 -XX:+AlwaysPreTouch 的方式在JVM啓動時就將所有的內存進行初始化.
這樣的話 JVM使用的內存就非常穩定了.
但是這樣存在兩個缺點和一個優點:
缺點: 啓動時間會變慢, 增加了內存申請了初始化的時間損耗
JVM實際使用內存的曲線會失去效果, 無法看到堆區在哪段時間突然佔用量升高.
優點: 內存都申請好了, 不會出現使用時先找操作系統申請的時間開銷.
關於JVM的內存調優
JVM的堆區能大盡量大, 能夠避免很多垃圾代碼對堆區內存的擠佔.
但是需要注意.JVM在大於32G的堆區時會取消指針壓縮, 會多佔用一部分堆棧指針使用的內存.
另外如果非專用服務器, 建議還是需要留出足夠操作系統使用的內存.避免出現OOM-killer的現象.
如果是JVM的out-of-memory可以使用oom後事件重新啓動服務.
-XX:OnOutOfMemoryError=/script/restart.sh
如果是容器運行, 可以使用 --restart=always的方式自動重啓
如果是進程, 可以使用 systemd 服務的方式 增加restart 啓動來解決問題
需要注意的是, 如果產品內有很多 相對路徑的用戶, 需要在systemd的服務裏面添加:
WorkingDirectory=/xxxx/ 目錄的方式來規避.
關於OOM後的處理參數
-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=dump.hprof 自動轉儲.
-XX:OnOutOfMemoryError=/xxxx/startup-linux.sh 自動重啓
-XX:+ExitOnOutOfMemoryError 自動退出, 不建議,很難排查問題.
-XX:+CrashOnOutOfMemoryError 建議使用第一個,如果產品要求立即啓動可以使用第二個.
典型的啓動腳本
nohup java -Xms24g -Xmx24g -Xmn8g -verbose:gc -XX:+PrintGCDateStamps
-XX:+PrintGCDetails -Xloggc:log/gc-%t.log -XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=2 -XX:GCLogFileSize=100M -XX:+CrashOnOutOfMemoryError
-jar app.jar >/dev/null 2>&1 &
# 注意 new 區域的大小是有根據業務場景來 有的都是大對象可以 new小一些
# 有的都是經常動態形成的對象, 可以設置的new區域大一些. 如果youngGC數據特別多, 可以考慮增加new區域.
# 如果老年代GC很多需要考慮增加老年代以及擴展堆區.
# 如果堆區內存無限制增加並且一直在GC要考慮是否存在內存泄漏的點.