简介
container: 资源隔离、平台无关, 限制cpu、mem等资源
java不知道自己运行在container里,以为它看到的资源都能用。结果:java工作在资源充足的
给大家推荐一个程序员学习交流群:702895049。群里有分享的视频,还有思维导图
群公告有视频,都是干货的,你可以下载来看。主要分享分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频。
详述
程序运行的两个核心资源:cpu和mem,其他资源或许也有限制,暂不涉及。
cpu
jvm检测可用cpu个数来优化运行时,影响jvm后台做的一些决策。
影响
所以ump的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传参数