记一次jvm闲置,但是应用进程占用高内存
jvm参数设置
ENV JAVA_OPTS="-Xrs -Dfile.encoding=utf-8 \
-Xmx2000m -Xms1200m -Xmn750m -Xss256k -XX:MetaspaceSize=400m -XX:MaxMetaspaceSize=800m \
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly \
-XX:CMSInitiatingOccupancyFraction=75 \
-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps \
-XX:+PrintHeapAtGC -Xloggc:/home/ewei/dump/open_gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/ewei/dump/open_java_error.hprof"
现象
通过jstat查看当时jvm堆是闲置的
java进程占用空间的计算规则
参考:https://www.cnblogs.com/LQBlog/p/9200810.html#_label8
分析内存泄露
1.可以dump文件,通过上面jstat 看到当时jvm使用空间是闲置的
怀疑是否是计算规则非堆内存和heap导致准备使用NAT进行分析,我的理解有一定溢出,但是不应该很大
NAT分析参考:https://blog.csdn.net/ruanchengshen/article/details/121291173 根据Java官方文档,开启NMT会有5%-10%的性能损耗;
可能原因分析
操作系统 与 JVM的内存分配
JVM 的自动内存管理,其实只是先向操作系统申请了一大块内存,然后自己在这块已申请的内存区域中进行“自动内存管理”。JAVA 中的对象在创建前,会先从这块申请的一大块内存中划分出一部分来给这个对象使用,在 GC 时也只是这个对象所处的内存区域数据清空,标记为空闲而已,并不会将空闲内存归还给操作系统
为什么不把内存归还给操作系统?
JVM 还是会归还内存给操作系统的,只是因为这个代价比较大,所以不会轻易进行。而且不同垃圾回收器 的内存分配算法不同,归还内存的代价也不同。
比如在清除算法(sweep)中,是通过空闲链表(free-list)算法来分配内存的。简单的说就是将已申请的大块内存区域分为 N 个小区域,将这些区域同链表的结构组织起来,就像这样:
每个 data 区域可以容纳 N 个对象,那么当一次 GC 后,某些对象会被回收,可是此时这个 data 区域中还有其他存活的对象,如果想将整个 data 区域释放那是肯定不行的。
所以这个归还内存给操作系统的操作并没有那么简单,执行起来代价过高,JVM 自然不会在每次 GC 后都进行内存的归还。
如何做到归还
通过xms不等于xmx来设置参考 搜索"xms注意事项" 因为归还有成本所以我们一般设置-xmx 和-xms是一致
但是这个归还内存的机制,在不同的垃圾回收器,甚至不同的 JDK 版本中还不一样! 如在:CMS垃圾回收器 可能在乎停顿时间 full gc不一定会回收完,就算回收完也不会释放给操作系统,需要自己调用System.gc();每调一次会释放内存到操作系统;基于下面测试数据可以看出来,jdk8默认则是释放回收空闲的50%
测试代码
@Controller public class HelloWordController { List<byte[]> arrList=new LinkedList<>(); @RequestMapping("/hello") @ResponseBody public String test(HttpServletRequest request) { for(int i=0;i<100;i++) { arrList.add(new byte[1024333]); } return String.valueOf(arrList.size()); } @RequestMapping("/clean") @ResponseBody public String clean(HttpServletRequest request){ arrList.clear(); return "true"; } @RequestMapping("/remove") @ResponseBody public String remove(HttpServletRequest request){ for(int i=0;i<100;i++) { arrList.remove(arrList.size() - 1); } return String.valueOf(arrList.size()); } @RequestMapping("/gc") @ResponseBody public String gc(HttpServletRequest request){ System.gc(); return "true"; } }
参数 -Xrs -server -verbose:gc -Xms200m -Xmx1024m -XX:MaxHeapFreeRatio=40 -XX:MetaspaceSize=200m -XX:MaxMetaspaceSize=300m -Xss256k -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC ------------------------------------CMS------------------------------- 调用hello次数:10 调用remove 8次释放剩余2 gc前进程占用1095M gc后进程占用1087M GC前: S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 34944.0 34944.0 0.0 30014.8 279616.0 279616.0 699072.0 698679.4 33792.0 31419.8 4352.0 3951.5 19 0.360 20 0.419 0.779 full gc后扩容GC后: jstat -gc 31509 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 34944.0 34944.0 0.0 1041.0 279616.0 102123.5 699072.0 607755.3 33792.0 31430.6 4352.0 3947.0 19 0.369 72 0.515 0.884 ------------------------------------jdk8默认------------------------------- 参数 -Xms200m -Xmx1024m -XX:MaxHeapFreeRatio=40 -XX:MetaspaceSize=200m -XX:MaxMetaspaceSize=300m -Xss256k -XX:LargePageSizeInBytes=128M -XX:SoftRefLRUPolicyMSPerMB=0 GC前: 调用hello接口:7 调用remove次数5次剩余2 gc前进程占用1018M gc后进程占用851M gc前 liqiangdeMacBook-Pro:~ liqiang$ jstat -gc 31627 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 88064.0 97280.0 59020.6 0.0 90112.0 15137.7 699392.0 645524.3 33792.0 31418.2 4352.0 3952.4 18 0.286 5 0.316 0.603 gc后 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 88064.0 97280.0 0.0 0.0 90112.0 26444.7 473088.0 283694.1 33792.0 31443.3 4352.0 3954.0 18 0.286 6 0.362 0.648 回收后:(59020+15137+645524+31418)-(26444+283694+31443)=399.919921875 释放了50%空闲
惰性分配
进程在申请内存时,并不是直接分配物理内存的,而是分配一块虚拟空间,到真正堆这块虚拟空间写入数据时才会通过缺页异常(Page Fault)处理机制分配物理内存,也就是我们看到的进程 Res 指标。
可以简单的认为操作系统的内存分配是“惰性”的,分配并不会发生实际的占用,有数据写入时才会发生内存占用,影响 Res。
所以,哪怕配置了Xms6G,启动后也不会直接占用 6G 内存,实际占用的内存取决于你有没有往这 6G 内存区域中写数据的。
注意事项:如果在jvm扩容的时候,如果系统可用内存不够的可申请内存 会触发系统的 oom kill 调进程
可能原因分析
top - 10:25:21 up 14 days, 12:09, 2 users, load average: 0.42, 0.35, 0.33 Tasks: 103 total, 1 running, 102 sleeping, 0 stopped, 0 zombie %Cpu(s): 3.5 us, 1.2 sy, 0.0 ni, 95.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 3733564 total, 272588 free, 2379472 used, 1081504 buff/cache KiB Swap: 0 total, 0 free, 0 used. 1119908 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 11126 root 20 0 5881916 1.9g 17356 S 8.7 52.2 52:56.66 java
jvm内存分配情况
jstat -gc 1
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
2560.0 2560.0 1114.1 0.0 250880.0 230464.7 512000.0 245147.4 164096.0 151513.7 14336.0 12074.5 4090 32.943 6 4.744 37.687
2560.0+2560.0+250880.0+512000.0+164096.0=932096KB=910.25M 进程占用1.9个G 溢出的1个G是哪里的,可能需要NAT才能看清楚怀疑是堆外内存泄露
怀疑是堆外内存泄露
上面jstat计算了元空间 那么就可能是堆外的其他堆外内存项占用,具体使用NAT分析 看是哪一块占有高
参数:-XX:MaxDirectMemorySize 限制堆外内存空间大小
Byte Buffer有两种:
heap ByteBuffer ->-XX:Xmx
1.一种是heap ByteBuffer,该类对象分配在JVM的堆内存里面,直接由Java虚拟机负责垃圾回收,
direct ByteBuffer->-XX:MaxDirectMemorySize
2.一种是direct ByteBuffer是通过jni在虚拟机外内存中分配的。通过无法查看该快内存的使用情况。只能通过top来看它的内存使用情况。
JVM堆内存大小可以通过-Xmx来设置,同样的direct ByteBuffer可以通过-XX:MaxDirectMemorySize来设置,此参数的含义是当Direct
ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC。注意该值是有上限的,默认应该受堆空间的可用空间的影响,最大为
sun.misc.VM.maxDirectMemory,在程序中中可以获得-XX:MaxDirectMemorySizel的设置的值。 Full GC没有回收调则会触发OOM
@RequestMapping("/max")
@ResponseBody
public String max(HttpServletRequest request){
return String.valueOf( sun.misc.VM.maxDirectMemory());
}
使用NMP加pmap进行分析
jstat -gc 1 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 3072.0 3584.0 0.0 1849.3 248832.0 46013.2 512000.0 282107.8 145024.0 133332.8 13184.0 11088.6 243 3.421 1 1.018 4.438 3072+3584+248832+512000+145024=912512+95+100+89+3 top KiB Mem : 3733552 total, 647148 free, 2143452 used, 942952 buff/cache KiB Swap: 0 total, 0 free, 0 used. 1365328 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 7666 root 20 0 5803260 1.6g 18280 S 11.7 44.7 6:41.01 java
pmap指标
1: java -XX:NativeMemoryTracking=summary -Djava.util.logging.config.file=/home/ewei/app/tomcat-ewei-open/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Dorg.apache.catalina.securit Address Kbytes PSS Dirty Swap Mode Mapping 0000000751800000 512000 512000 512000 0 rw-p [ anon ] 0000000770c00000 1195008 0 0 0 ---p [ anon ] 00000007b9b00000 256000 256000 256000 0 rw-p [ anon ] 00000007c9500000 596992 0 0 0 ---p [ anon ] 00000007edc00000 13184 13024 13024 0 rw-p [ anon ] 00000007ee8e0000 1035392 0 0 0 ---p [ anon ] 0000559deeca4000 4 4 0 0 r--p /usr/lib/jvm/java-1.8-openjdk/jre/bin/java 0000559deeca5000 4 4 0 0 r-xp /usr/lib/jvm/java-1.8-openjdk/jre/bin/java 0000559deeca6000 4 0 0 0 r--p /usr/lib/jvm/java-1.8-openjdk/jre/bin/java 0000559deeca7000 4 4 4 0 r--p /usr/lib/jvm/java-1.8-openjdk/jre/bin/java 0000559deeca8000 4 4 4 0 rw-p /usr/lib/jvm/java-1.8-openjdk/jre/bin/java 0000559df0037000 1530768 532588 532588 0 rw-p [heap] .....忽略其他小的值 ---------------- ------ ------ ------ ------ total 5804300 1623024 1604816 0
有点怀疑是heap这一项
正常如果NMP配置的Detail可以根据地址块来判断对应jvm的哪些项 但是我们线上不能执行 如以下detail的输出
14179: Native Memory Tracking: Total: reserved=653853KB, committed=439409KB - Java Heap (reserved=262144KB, committed=262144KB) (mmap: reserved=262144KB, committed=262144KB) - Class (reserved=82517KB, committed=81725KB) (classes #17828) (malloc=1317KB #26910) (mmap: reserved=81200KB, committed=80408KB) - Thread (reserved=20559KB, committed=20559KB) (thread #58) (stack: reserved=20388KB, committed=20388KB) (malloc=102KB #292) (arena=69KB #114) - Code (reserved=255309KB, committed=41657KB) (malloc=5709KB #11730) (mmap: reserved=249600KB, committed=35948KB) - GC (reserved=1658KB, committed=1658KB) (malloc=798KB #676) (mmap: reserved=860KB, committed=860KB) - Compiler (reserved=130KB, committed=130KB) (malloc=31KB #357) (arena=99KB #3) - Internal (reserved=5039KB, committed=5039KB) (malloc=5007KB #20850) (mmap: reserved=32KB, committed=32KB) - Symbol (reserved=18402KB, committed=18402KB) (malloc=14972KB #221052) (arena=3430KB #1) - Native Memory Tracking (reserved=2269KB, committed=2269KB) (malloc=53KB #1597) (tracking overhead=2216KB) - Arena Chunk (reserved=187KB, committed=187KB) (malloc=187KB) - Unknown (reserved=5640KB, committed=5640KB) (mmap: reserved=5640KB, committed=5640KB) . . . Virtual memory map: [0xceb00000 - 0xcec00000] reserved 1024KB for Class from [0xced00000 - 0xcee00000] reserved 1024KB for Class from . . . [0xcf85e000 - 0xcf8af000] reserved and committed 324KB for Thread Stack from [0xd4eaf000 - 0xd4f00000] reserved and committed 324KB for Thread Stack from [0xf687866e] Thread::record_stack_base_and_size()+0x1be [0xf68818bf] JavaThread::run()+0x2f [0xf67541f9] java_start(Thread*)+0x119 [0xf7606395] start_thread+0xd5 [0xd5a00000 - 0xe5a00000] reserved 262144KB for Java Heap from . . . [0xe5e00000 - 0xf4e00000] reserved 245760KB for Code from [0xf737f000 - 0xf7400000] reserved 516KB for GC from [0xf745d000 - 0xf747d000] reserved 128KB for Unknown from [0xf7700000 - 0xf7751000] reserved and committed 324KB for Thread Stack from [0xf7762000 - 0xf776a000] reserved and committed 32KB for Internal from