项目运行环境:

运行环境:k8s+docker+open-jdk11+springBoot
公司的项目都是使用kubernate+docker来管理、运行应用。


问题描述:

发现项目出现多次oom。排查到的原因有两个,1是分配的堆内存太小;2是代码设计原因(之后写出来总结)


原因分析:

进入容器,使用 jhsdb jmap --heap --pid [PID] 查看分配的堆大小
输出结果:

JVM version is 11.0.9.1+1

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 262144000 (250.0MB)
   NewSize                  = 5570560 (5.3125MB)
   MaxNewSize               = 87359488 (83.3125MB)
   OldSize                  = 11206656 (10.6875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 43319296 (41.3125MB)
   used     = 17549536 (16.736541748046875MB)
   free     = 25769760 (24.575958251953125MB)
   40.5120526427761% used
Eden Space:
   capacity = 38535168 (36.75MB)
   used     = 14510800 (13.838577270507812MB)
   free     = 24024368 (22.911422729492188MB)
   37.655992572810376% used
From Space:
   capacity = 4784128 (4.5625MB)
   used     = 3038736 (2.8979644775390625MB)
   free     = 1745392 (1.6645355224609375MB)
   63.51702964469178% used
To Space:
   capacity = 4784128 (4.5625MB)
   used     = 0 (0.0MB)
   free     = 4784128 (4.5625MB)
   0.0% used
tenured generation:
   capacity = 95477760 (91.0546875MB)
   used     = 69527288 (66.30638885498047MB)
   free     = 25950472 (24.74829864501953MB)
   72.82040131649507% used

可以看到最大堆内存只有250MB,也是很奇怪为什么会这么小,之前也没怎么关注这块。
查询发现,k8s会限制容器的运行内存,所以使用 kubectl describe [pod] 查看节点的详情,可以看到容器限制的内存大小:

Containers:
  your-appliction-name:
    Limits:
      cpu:     500m
      memory:  1000Mi
    Requests:
      cpu:      200m
      memory:   1000Mi

想不懂,限制的是1000mib,为什么实际分配的只有250mb。

查询发现:

-XX:+UseContainerSupport ,此参数用于使 JVM在分配堆大小时考虑容器内存限制,而不是主机配置。

在早期jdk版本里,应用运行在容器里,jvm无法感知到容器环境的存在,所以对容器资源的限制比如内存或者cpu等都无法生效,只能获取到服务器的配置。那么分配jvm内存超过容器限制,容器会被kill掉。
为了解决这个问题,从jdk10开始,加入了 +UseContainerSupport(默认情况下启用),通过这个特性,可以使得JVM在容器环境分配合理的堆内存。 并且,在JDK8U191版本之后,这个功能引入到了JDK 8,而JDK 8是广为使用的JDK版本。
注:这里具体jdk版本没经过验证。

-XX:MaxRAMPercentage=25(默认25),指定java堆最大大小占虚拟机可用总内存的百分比

也就是这个参数,造成 容器限制的最大内存为1000mib,java堆实际被分配到的最大为250mib。

-XX:InitialRAMPercentage =1.5(默认1.5),指定java堆初始大小占虚拟机可用总内存的百分比

所有堆内存只有250mb的原因找到了。


解决方案:

设置jvm参数:-XX:+UseContainerSupport -XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage =70
把jvm堆内存占 容器限制的总内存70%。重启容器,使用jmap命令进行查询,确实发生改变。
注意:这个占比,需要根据自己的应用来进行设置,因为除堆内存外,jvm还会占用容器的部分内存,容器运行也需要一部分运行,当容器内存不足时,容器会产生OOMKill,将容器kill进行重启。


学习参考链接:

链接:
-XX:[+|-]UseContainerSupport.
容器环境的JVM内存设置最佳实践.

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐