问题:实际占用内存远大于分配内存,无OOM
服务器:120.133.0.233, 实例:api-gateway-analyze
1、查找问题进程
top , shfit + M 让程序按照内存占用大小排序
查看当前进程分配的实际内存大小
2、初步排查问题
回忆JVM内存构成,在JDK8版本,主要由Heap + Other(Metaspace + JVM运行所需其他),参考:好图收藏
按照以上思路猜测为Metaspace超出限制
命令:jstat -gc 104144 1000 3
结果很遗憾,meta占用空间只有几十M
3、开启NMT排查
实例启动时加上 -XX:NativeMemoryTracking=detail 参数
执行:jcmd 104144 VM.native_memory summary
简单分析各组件占用内存大小:Symbol过于显眼,占用高达1.8G。
使用以下组合命令可以设计基准点,过一段时间再执行,查看每个模块的增长量
jcmd 104144 VM.native_memory baseline
jcmd 104144 VM.native_memory summary.diff
目前问题锁定,Symbol原因
4、Symbols中存储说明
让我们从字符串开始,它是应用程序和库代码中最常用的数据类型之一。由于它们无处不在,因此它们通常占据堆的很大一部分。
如果大量的这些字符串包含相同的内容,则将浪费堆的很大一部分。为了节省一些堆空间,我们可以存储每个String的一个版本,
并使其他版本引用存储的版本。此过程称为String Interning。
由于JVM只能内生编译时间字符串常量,因此我们可以对要内生的字符串手动调用String.intern()方法。JVM将内部字符串存储在称
为字符串表(也称为字符串池)的特殊本地固定大小的哈希表中。我们可以通过-XX:StringTableSize调整标志来配置表的大小(即存储桶数)。
除了字符串表之外,还有另一个本机数据区域,称为运行时常量池。JVM使用此池存储必须在运行时解析的常量,如编译时数字文字,方法和字段引用。
5、解决方案
解决方案:增加-XX:StringTableSize参数配置
问题点:网关分析为了提高并发,对用户的ip地址、uid等属性大量使用intern()方法,这样会让大量的不重复的信息写入常量池中,不会占用heap内存,也不会OOM。对于同一服务器下的其他应用是一种灾难。
6、其他手段
1、查看进程内存占用
ps -p 104144 -o rss,vsz
2、进程内存分配(倒序)
pmap -x 104144 | sort -n -k3
3、gdb来dump内存并查看
1、gdb attach 104144
2、dump memory a.dump 0x7f8f20000000 0x7f8f2c000000
3、view a.dump
参考文章
【Native Memory Tracking in JVM】Native Memory Tracking in JVM | Baeldung
【JVM内存非典型术语介绍】JVM内存非典型术语介绍(shallow/retained/rss/reserved/committed) - 简书
【Java堆外内存增长问题排查】Java堆外内存增长问题排查-CSDN博客
【Java堆外问题小结】JAVA堆外内存排查小结 - 知乎