1. 程式人生 > >容器(docker)中執行java需關注的幾個小問題

容器(docker)中執行java需關注的幾個小問題

簡介

  • container: 資源隔離、平臺無關, 限制cpu、mem等資源


  •   java不知道自己執行在container裡,以為它看到的資源都能用。結果:java工作在資源充足的

詳述

程式執行的兩個核心資源:cpu和mem,其他資源或許也有限制,暫不涉及。

cpu

jvm檢測可用cpu個數來優化執行時,影響jvm後臺做的一些決策。

影響

實驗

實驗所用容器宿主機器是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傳引數