容器(docker)中執行java需關注的幾個小問題
簡介
container: 資源隔離、平臺無關, 限制cpu、mem等資源
java不知道自己執行在container裡,以為它看到的資源都能用。結果:java工作在資源充足的
詳述
程式執行的兩個核心資源:cpu和mem,其他資源或許也有限制,暫不涉及。
cpu
jvm檢測可用cpu個數來優化執行時,影響jvm後臺做的一些決策。
影響
java.lang.Runtime 所以ump的jvm監控資料一直以來都是不準確的
GC 主要是執行緒數
實驗
實驗所用容器宿主機器是4核CPU16G記憶體
java 6/7/8/9
docker run --cpus 1 -m 1G -it adoptopenjdk/openjdk9:latest # 給1核 jshell -J-Xmx512M -v # 啟動jshell Runtime.getRuntime().availableProcessors() # 結果是不是1!!!
java 10
docker run --cpus 1 -m 1G -it adoptopenjdk/openjdk10:latest # 給1核 jshell -J-Xmx512M -v # 啟動jshell Runtime.getRuntime().availableProcessors() # 結果是1
對策
java 10才解決這個問題
java 10之前:手動設定jvm相關的選項,如:
ParallelGCThreads
ConcGCThreads
G1ConcRefinementThreads
CICompilerCount / CICompilerCountPerCPU
java 10+:
UseContainerSupport, 預設開啟
mem
jvm自動檢測拿到的是宿主機的記憶體資訊,它無法感知容器的資源上限 主要需要關注:動態記憶體管理(上下限,預設值) 我們先了解下java程序記憶體消耗在哪裡
記憶體結構
JFTR: 分代:垃圾收集的一大策略,並不是所有GC演算法都分代哦
記憶體總量 = 廣義堆記憶體 + 廣義堆外記憶體
廣義堆內記憶體 = 狹義堆內記憶體 + 永久代(Perm) 狹義對內記憶體 = 新生代(New) + 老年代(Old) # Xmx Xms 新生代(New) = S0 + S1 + Eden # NewSize NewRatio SurvivorRatio 廣義堆外記憶體 = 狹義堆外記憶體(directbytebuffer) # MaxDirectMemorySize,netty/mina等高效能網路通訊常用,具體不是很瞭解 + java棧 # 需關注,執行緒數 * ThreadStackSize(Xss) + native棧 # 不大,執行緒數 * VMThreadStackSize + CICompilerCount * CompilerThreadStackSize + pc暫存器 # 可忽略 + jni(如帶用c/c++ malloc)# 這個不可控,一般忽略就好 java 8之後Metaspace替代了Perm,從廣義堆內轉移到廣義對外,why? - Perm連續、固定、jvm啟動即claim到max,浪費,不好控制 - 程式碼實驗發現Perm不受Xmx限制`java -Xmx10M -Xmx10M -XX:PermSize=100M -XX:MaxPermSize=100M -version` - Metaspace的實現類似鏈式結構,預設值-1(無限大,取決於kernel讓用多少),在fullgc時gc 上面關於廣義/狹義記憶體的定義是參考別人的資料後,自己定義的。。。不喜勿噴 - 官方對於Perm的定義搖擺不定,前後矛盾,讓我自己很困惑 正方:在 http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html http://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdfOne important change in Memory Management in Java 8 反方:不在 https://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html https://stackoverflow.com/questions/1262328/how-is-the-java-memory-pool-divided https://blogs.oracle.com/jonthecollector/presenting-the-permanent-generation https://www.yourkit.com/docs/kb/sizes.jsp https://blog.codecentric.de/en/2010/01/the-java-memory-architecture-1-act/ - 與Metaspace替換Perm有點兒關係吧
綜上,我們需要關注下面幾類引數是否合理: – 狹義堆內 Xmx – 狹對堆外 MaxDirectMemorySize – Perm/Metaspace MaxPermSize/MaxMetaspaceSize
需關注的選項預設值
- Xmx: 1/4 * 實體記憶體 # 此處的實體記憶體為Runtime看到的記憶體(大多時候是宿主機的記憶體) - MaxDirectMemorySize - Xmx 未設定,實體記憶體 - Xmx 設定了, Xmx - S0(1/10 * Xmx) = 0.9 * Xmx # why? SurvivorRatio預設值8 - MaxPermSize: 預設64M [5.0+ 64 bit: 64M * 1.3 = 85M](http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html) - MaxMetaspaceSize: -1,無限制
實驗
實驗所用容器宿主機器是4核CPU16G記憶體
java 7
docker run -m 1G -it openjdk:7u181 java -XX:+PrintFlagsFinal -version | grep MaxHeapSize # 結果是 16G / 4 = 4G
java 8
docker run -m 1G -it adoptopenjdk/openjdk8:latest java -XX:+PrintFlagsFinal -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -version | grep MaxHeapSize # 結果是 1G / 4 = 256M
java 9
docker run -m 1G -it adoptopenjdk/openjdk9:latest java -XX:+PrintFlagsFinal -version | grep MaxHeapSize # 結果是 16G / 4 = 4G java -XX:+PrintFlagsFinal -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -version | grep MaxHeapSize # 結果是 1G / 4 = 256M
java 10
docker run -m 1G -it adoptopenjdk/openjdk10:latest # 給1G jshell -v # 啟動jshell java -XX:+PrintFlagsFinal -version | grep MaxHeapSize # 結果是 1G / 4 = 256
對策
java5/6/7/8u131-:務必設定記憶體選項
懶人可考慮,雖然也不準確, 參考前面對jvm記憶體結構的分析 java -Xmx`cat /sys/fs/cgroup/memory/memory.limit_in_bytes`
java8u131+和java9+
java 8u131+和java 9+
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
java 8u191+ UseContainerSupport預設開啟,backported;java 9暫未backport這個feature
java10+
使用最新版就好了,UseContainerSupport預設開啟
擴充套件
排查工具
jvm支援的選項
生產
java -XX:+PrintFlagsFinal 2>/dev/null
試驗
java -XX:+PrintFlagsFinal -XX:+UnlockExperimentalVMOptions 2>/dev/null | grep experimental
可熱更新
java -XX:+PrintFlagsFinal -XX:+UnlockExperimentalVMOptions 2>/dev/null | grep manageable 如:熱開啟gc日誌 jinfo -flag +PrintGC ${pid} # 官方文件說jinfo是實驗工具,截至java 10,它都還在,[不過jhat在java9被去掉了](http://openjdk.java.net/jeps/241) jinfo -flag +PrintGCDetails ${pid}
容器內執行jstat/jps/jmapOOM問題
工具類是用C++包裝的java程式碼,它們不識別常規的傳給jvm的引數,如最大記憶體。 在強悍的(超級大記憶體)宿主機器下,容器內經常因為OOM問題啟動不了這些工具。
java 6及之前: 通過java呼叫
java -cp ${JAVA_HOME}/lib/tools.jar -Xmx100M sun.tools.jstack.JStack ${pid}
java 7+
jstack -J-Xmx100M -v # -J選項給jvm傳引數