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

簡介

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


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

給大家推薦一個程序員學習交流羣:702895049。羣裏有分享的視頻,還有思維導圖

羣公告有視頻,都是乾貨的,你可以下載來看。主要分享分佈式架構、高可擴展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分佈式項目實戰學習架構師視頻。

詳述

程序運行的兩個核心資源: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傳參數


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章