Docker内JVM参数的简单学习

Docker内JVM参数的简单学习


背景

公司内部有K8S的项目. 
基于K8S内容器的JVM参数的设置与标准虚拟机运行不太一样.
产品内部的启动脚本有一个设置, 在内存大于16G的情况下
默认取内存总量的四分之一作为堆区. 
在虚拟化时 这样处理其实是没有问题的, 但是容器内运行可能存在问题.
Docker 运行能够看到宿主机所有的内存, 
这样计算可能会超过Docker容器的资源限制
会导致被OOM-killer 需要注意 OOM-killer与 JVM的out of memory 是不一样的. 
基于此, 最近几天研究了下容器内的JVM参数设置, 简要总结如下

关于JVM针对容器的支持情况

JDK1.8 是在191update 里面添加了对容器运行的识别与优化.
前期的版本可以使用参数进行开启, 但是因为我们不用这么低版本的JDK
这里只是简单记录一下的版本的参数: 
-XX:+UnlockExperimentalVMOptions 
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=2
知识来源: 
https://zhuanlan.zhihu.com/p/120168070?utm_source=wechat_session
需要注意 JVM使用的内存有:
总内存 = Heap + Code Cache + Metaspace + Symbol tables + Other JVM structures +
 Thread stacks + Direct buffers + Mapped files + Native Libraries + Malloc overhead + ...

关于JVM针对容器的支持情况

高于JDK1.8u191版本的JDK就会好很多
有几个浮点类型的参数可以使用
-XX:{Initial|Min|Max}RAMPercentage
使用者几个参数之后会根据容器运行的资源限制自动进行堆区的划分.
注意有资料说(还是上一个知乎连接), 这里必须使用浮点类型的比如60.0和80.0
这样就会默认将 limits的限制的 60% 作为堆区. 
其他内存用作栈区, codecache 方法区 源数据库 本地内存等. 

注意 如果limits 限制比较小, 建议还是不能大部分用于堆区的, 
避免非堆区出现OOM或者是栈溢出.

关于JVM内存占用的情况

在非容器的-Xms,-Xmx等参数以及-XX:{Initial|Min|Max}RAMPercentage的参数
按照阿里和Oracle JVM官方文档说明, 建议初始化/最小/最大都设置成一样的大小
避免因为JVM 堆区的收缩导致STW的时间增加. 影响客户体验. 

但是虽然有最小和初始化的参数, 但是查看内存的resident memory 时发现启动时以及
启动后的一段时间 内存是与设置的数据不一样的. 

其实核心问题是JVM为了启动的性能, 不会将申请的虚拟化内存直接进行initial处理.
只有用到时才会进行向操作系统申请具体的物理内存. 此时才会有具体的resident memory的变化.

需要注意可以使用 -XX:+AlwaysPreTouch 的方式在JVM启动时就将所有的内存进行初始化.
这样的话 JVM使用的内存就非常稳定了. 

但是这样存在两个缺点和一个优点:
缺点: 启动时间会变慢, 增加了内存申请了初始化的时间损耗
      JVM实际使用内存的曲线会失去效果, 无法看到堆区在哪段时间突然占用量升高.
优点:  内存都申请好了, 不会出现使用时先找操作系统申请的时间开销. 

关于JVM的内存调优

JVM的堆区能大尽量大, 能够避免很多垃圾代码对堆区内存的挤占. 
但是需要注意.JVM在大于32G的堆区时会取消指针压缩, 会多占用一部分堆栈指针使用的内存.

另外如果非专用服务器, 建议还是需要留出足够操作系统使用的内存.避免出现OOM-killer的现象.

如果是JVM的out-of-memory可以使用oom后事件重新启动服务. 
-XX:OnOutOfMemoryError=/script/restart.sh 
如果是容器运行, 可以使用 --restart=always的方式自动重启
如果是进程, 可以使用 systemd 服务的方式 增加restart 启动来解决问题
需要注意的是, 如果产品内有很多 相对路径的用户, 需要在systemd的服务里面添加:
WorkingDirectory=/xxxx/ 目录的方式来规避.

关于OOM后的处理参数

-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=dump.hprof    自动转储.
-XX:OnOutOfMemoryError=/xxxx/startup-linux.sh                 自动重启
-XX:+ExitOnOutOfMemoryError                                   自动退出, 不建议,很难排查问题. 
-XX:+CrashOnOutOfMemoryError                                  建议使用第一个,如果产品要求立即启动可以使用第二个.

典型的启动脚本

nohup java -Xms24g -Xmx24g -Xmn8g -verbose:gc -XX:+PrintGCDateStamps 
-XX:+PrintGCDetails -Xloggc:log/gc-%t.log -XX:+UseGCLogFileRotation 
-XX:NumberOfGCLogFiles=2 -XX:GCLogFileSize=100M -XX:+CrashOnOutOfMemoryError 
-jar app.jar  >/dev/null 2>&1 &

# 注意 new 区域的大小是有根据业务场景来 有的都是大对象可以 new小一些
# 有的都是经常动态形成的对象, 可以设置的new区域大一些. 如果youngGC数据特别多, 可以考虑增加new区域.
# 如果老年代GC很多需要考虑增加老年代以及扩展堆区. 
# 如果堆区内存无限制增加并且一直在GC要考虑是否存在内存泄漏的点. 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章