簡介
container: 資源隔離、平臺無關, 限制cpu、mem等資源
java不知道自己運行在container裏,以爲它看到的資源都能用。結果:java工作在資源充足的
給大家推薦一個程序員學習交流羣:702895049。羣裏有分享的視頻,還有思維導圖
羣公告有視頻,都是乾貨的,你可以下載來看。主要分享分佈式架構、高可擴展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分佈式項目實戰學習架構師視頻。
詳述
程序運行的兩個核心資源: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傳參數