率很高的问题分析"/>
Pod的内存使用率很高的问题分析
生产环境中在流量高峰期出现pod内存使用率很高,pod批量重启,错误日志中还有OOM相关信息。
查看堆内存的使用值
Pod使用的内存不能直接在pod中通过top命令查看,这种方式看到的是pod所在node的资源使用情况。想查看pod的资源使用情况需要用kubectl top pod xxx
命令。但这也只能看到pod的内存使用。
那如何查看jvm的堆内存使用呢?
通过kubectl exec -it pod-name /bin/bash
连接到pod内部的shell,然后执行jmap,jstat等命令来查看JVM堆内存情况。
如果接入了Prometheus,则可以通过
sum(jvm_memory_used_bytes{pod="$pod", area="heap"})
sum(jvm_memory_committed_bytes{pod="$pod", area="heap"})
sum(jvm_memory_max_bytes{pod="$pod", area="heap"})
这几项指标来查看当前正在使用,已申请的和最大的堆内存大小。
通过查看指标发现堆内存很小,大约为pod内存的1/4,这是一个很小值,显然是不合理的。
pod中堆内存参数
在应用容器化之前,应用部署在物理机器上,通常会通过-Xms
和-Xmx
来设置堆的最小值和最大值。但出现容器化之后,通过这种静态值来设置堆内存大小就不合适了,因为pod的内存大小是可设置的,不像物理机一样是固定的。
因此,JDK8U191为了适配docker容器新增了几个参数,分别是:
MaxRAMPercentage
,InitialRAMPercentage
和MinRAMPercentage
,同时将InitialRAMFraction
,MaxRAMFraction
和MinRAMFraction
标记为deprecated状态。
比较违反直觉的是,MinRAMPercentage
并不是最小堆内存的意思,而是当实际内存小于96m的时候,用于计算最大堆内存,也就是说,最大堆内存计算方式如下:
phys_mem * MinRAMPercentage / 100 (if this value is less than 96M)
MAX(phys_mem * MaxRAMPercentage / 100, 96M)
通常情况下实际内存不可能小于96m,忽略MinRAMPercentage
即可。那实际生效的就是初始化大小比例和最大比例两个参数生效。这两个参数的默认是多少呢?
我们进入pod,运行命令
java -XX:+PrintFlagsFinal -version | grep RAMPercentage
可以看到结果如下:
double InitialRAMPercentage = 1.562500
double MaxRAMPercentage = 25.000000
double MinRAMPercentage = 50.000000
最大堆内存为实际内存的25%,这也是为什么在生产环境中使用的堆内存始终只有pod内存的1/4。
因为堆内存太小,当流量高峰出现时OOM就可以理解了。但还是无法解释pod内存使用率很高的问题(堆外内存应该占用不了多少内存)。
参数优化
分析了OOM原因之后,添加了两个参数:
-XX:InitialRAMPercentage=25
-XX:MaxRAMPercentage=70
同时也将pod内存从1.5G调大到了2G。
重新部署之后服务正常,不再出现pod重启。每个pod的堆内存使用在1个G左右,而不是原来300多M。
优化后,理论上每个pod的最大堆内存可使用至1.4G,是够用的。
但是pod内存使用率还是保持高位,从而导致根据pod设置的hpa扩缩容策略失效,只会扩容,不会缩容。
直接给出原因:JVM在流量高峰期使用了较高的内存,随后该内存一直由JVM管理,并不会归还给操作系统。
这似乎有点难以理解,在我之前的认知里面,通过垃圾回收器回收的内存应该要归还给操作系统,但其实不是这样的。实际上,JVM向操作系统申请内存是有代价的,如果每次gc后将内存归还,然后用到的时候再申请,这将极大损耗JVM的性能。
因此默认情况下,JVM是不会在短时间内主动归还未使用的内存的。当然,具体的机制取决于JDK的版本和垃圾回收器。明确的官方说明我并没有从google上搜索到,但有一点是可以确认的,OpenJDK遵循这一原则1,并且在OpenJDK12版本中引入了一个新功能:鼓励G1垃圾回收器自动将未使用的内存归还给操作系统。如下所示:
如何降低pod的内存使用率
JVM中有两个参数,分别是MinHeapFreeRatio
和MaxHeapFreeRatio
,含义是允许JVM保留的最小和最大的未使用内存比率,默认值分别是40和70。其中MaxHeapFreeRatio
在OpenJDK11之后的版本中被废弃。但有一些其他参数可以控制堆空间的收缩行为:
- -XX:MinHeapFreeRatio:该参数用于设置最小堆空闲空间比例的阈值。当堆空间的空闲空间低于该阈值时,JVM会尝试扩展堆空间以提供更多空闲空间,在G1中不生效。
- -XX:GCTimeRatio:该参数用于设置垃圾收集时间与应用程序执行时间的比率。较高的值将导致JVM更积极地回收内存并进行堆空间的收缩。在G1中不生效。
- -XX:AdaptiveSizePolicy:该参数启用自适应堆大小调整策略,JVM将根据应用程序的行为动态调整堆大小。这可以包括堆空间的扩展和收缩。
但即使发生堆收缩,未使用的内存是否会归还给操作系统,还是取决于具体的JVM和垃圾回收器。除非进行显式的System.gc()的调用。
最终未找到合适的将JVM内存归还给操作系统的方法,因此只能去掉了HPA中memory的策略,只保留CPU策略。
只保留CPU扩缩容策略引起了一些其他的问题,当然,这不在本文讨论的范围之内。
参考资料
[1]..8/nodes/clusters/nodes-cluster-resource-configure.html#nodes-cluster-resource-configure-jdk-unused_nodes-cluster-resource-configure:~:text=to%20be%20calculated.-,Understanding%20how%20to%20encourage%20the%20JVM%20to%20release%20unused%20memory%20to%20the%20operating%20system,-By%20default%2C%20the
å
更多推荐
Pod的内存使用率很高的问题分析
发布评论