为什么JVM占用了超过-Xmx配置的内存?

为什么JVM占用了超过-Xmx配置的内存?

2022-06-05893阅读5分钟
 

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

前言

一般JVM我们都会配置上 Xmx参数,限制堆内存的使用

但是在top指令或者其他的指令上看到的值却不是我们期望的Xmx的数字

实际上Xmx占用的内存只是JVM所占内存的一部分,JVM经过各种版本的演化,实际上有了许多的变化

可以从下图看出

image.png

一个比较大的变化是 JDK8 已经没有了PermSpace, 增加了 metaspace 的区域,用来保存常量池和类常量池,这部分的内存不是分配在堆上的,而是分配在堆外的,通常如果不设置的话,这部分会无限增长,使用 jmap -heap 指令看出maxmetaspacesize的值非常大,可以认为就是没有限制。

这部分的堆外内存只有在full gc时才会被回收

image.png

metaspace

可以通过如下几个参数对metaspace 的大小进行限制

  • -XX:MetaspaceSize=N :这个参数是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加上线也可能降低。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为21810376B(大约20.8M)
  • -XX:MaxMetaspaceSize=N :这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。默认无上限。
  • -XX:MinMetaspaceFreeRatio=N :当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。在本机该参数的默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
  • -XX:MaxMetasaceFreeRatio=N :当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%。
  • -XX:MaxMetaspaceExpansion=N :Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)
  • -XX:MinMetaspaceExpansion=N :Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)

一个误解

使用jstat -gcutil的结果中的M代表的是元空间的使用比例。又例如:测试环境上某个服务为例,配置了-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m,通过jstat -gcutil pid查看M的值为98.32,即Meta区使用率也达到了98.32%
然后,再通过jstat -gc 4210 2s 3命名查看,结果如下图所示,计算MU/MC即Meta区的使用率确实达到了98.32%,但是MC,即Metaspace Capacity只有55296k,并不是参数MetaspaceSize指定的256m:

image.png

那么-XX:MetaspaceSize=256m的含义到底是什么呢?

其实,这个JVM参数是指Metaspace扩容时触发FullGC的初始化阈值,也是最小的阈值。这里有几个要点需要明确:

  • 如果没有配置-XX:MetaspaceSize,那么触发FGC的阈值是21807104(约20.8m),可以通过jinfo -flag MetaspaceSize pid得到这个值;jps -v也可以查看jvm的参数设置情况。
  • 如果配置了-XX:MetaspaceSize,那么触发FGC的阈值就是配置的值;
  • Metaspace由于使用不断扩容到-XX:MetaspaceSize参数指定的量,就会发生FGC;且之后每次Metaspace扩容都可能会发生FGC(至于什么时候会,比较复杂,跟几个参数有关);
  • 如果Old区配置CMS垃圾回收,那么扩容引起的FGC也会使用CMS算法进行回收;
  • 如果MaxMetaspaceSize设置太小,可能会导致频繁FullGC,甚至OOM;

建议

  • MetaspaceSizeMaxMetaspaceSize设置一样大;
  • 具体设置多大,建议稳定运行一段时间后通过jstat -gc pid确认且这个值大一些,对于大部分项目256m即可。目前我们的使用也是这一个值

\

实际的JVM内存占用如何计算

Max memory = [-Xmx] + [-XX:MaxPermSize] + number_of_threads * [-Xss]

所以实际如果我们配置了如下参数

-Xmx512m -Xss1m -MaxMetaSpaceSize=256m 的JVM实际占用的大小为  512+(1*线程数)+256

可以通过如下指令输出JVM 的内存占用,使用这个指令需要在JVM启动时添加参数 

java -XX:NativeMemoryTracking=detail

jcmd pid VM.native_memory

输出如下,可以看到整个JVM使用的内存就是由一下的这些部分组成的

image.png

总结

当时的服务实际上时占用了2.5G的内存,这部分因为当时没有加上跟踪参数,所以暂时还无法查看到

根据以上信息推论可以推论得到,占用的大头应该是metaspace

这部分的内存一开始并没有限制,不会因为这部分的内容触发 full gc,动态产生的class都会放在这个区域,进程运行的时间越长,这个区域就会越大,同时也会观察到实际的堆占用并没有增加

现在开发环境采用的参数为  -Xmx512m -Xss512k -XX:MaxMetaspaceSize=256m,需要观察一段时间看是否限制住了

也可以通过命令 jcmd pid VM.native_memory 建立的快照来对比发生泄漏的区域,找到问题的根源

 

转自 为什么JVM占用了超过Xmx配置的内存? - 掘金 (juejin.cn)

posted @ 2024-02-29 17:37  dint  阅读(35)  评论(0编辑  收藏  举报