GC优化案例6: metaspace占用率过高

对GC优化的案例进行的系列总结(六):

由于最近写的程序在运行一段时间后出现高cpu,然后不可用故进而进行排查,最终定位到由于metaspace引起fullgc,不断的fullgc又占用大量cpu导致程序最终不可用。下面就是这次过程的分析排查和总结,便于以后温故,同时也希望能给遇到同样问题的同学一些参考。 一 jvm的内存分配情况:

1

Eden Survivor1 Survivor2 Tenured Tenured 包含perm jdk<=7

gc类型分为:minor gc 和 major gc ,major的速度比minor慢10倍至少 发生在 young(主要是Survivor)区的gc称为 minor gc 发生在 old(Tenured)区的gc称为 major gc

1.问题描述

1

jstat -gcutil 26819 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 43.75 0.00 42.22 67.19 50.93 4955 30.970 4890 3505.049 3536.020

可以看到M(metaSpace使用率)的值是67.19,metaSpace使用率为67.19;O为42.22,old区使用率为42.22

1

top -H -p 26819 26821 appdev 20 0 6864m 1.2g 13m R 87.6 7.5 53:40.18 java 26822 appdev 20 0 6864m 1.2g 13m R 87.6 7.5 53:41.40 java 26823 appdev 20 0 6864m 1.2g 13m R 87.6 7.5 53:43.64 java 26824 appdev 20 0 6864m 1.2g 13m R 85.6 7.5 53:41.59 java 26825 appdev 20 0 6864m 1.2g 13m R 85.6 7.5 53:43.82 java 26826 appdev 20 0 6864m 1.2g 13m R 85.6 7.5 53:40.47 java 26827 appdev 20 0 6864m 1.2g 13m R 85.6 7.5 53:45.05 java 26828 appdev 20 0 6864m 1.2g 13m R 83.6 7.5 53:39.08 java

可以发现26821到26828的cpu使用率很高,26821转为16进制为68c5

12345678

jstack 26819 > 26819.text vim 26819.text 然后搜索68c5-68cc "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f0aa401e000 nid=0x68c5 runnable"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f0aa4020000 nid=0x68c6 runnable"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f0aa4021800 nid=0x68c7 runnable"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f0aa4023800 nid=0x68c8 runnable"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007f0aa4025800 nid=0x68c9 runnable"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007f0aa4027000 nid=0x68ca runnable"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007f0aa4029000 nid=0x68cb runnable"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007f0aa402a800 nid=0x68cc runnable

可以发现一致是full gc的线程在执行,占用cpu较高的资源,并且一致持续,表明一直达到了full gc的条件但是又不能回收掉内存从而占用大量cpu,导致程序不可用。 查看启动配置参数如下: -Xms1000m -Xmx1000m -XX:MaxNewSize=256m -XX:ThreadStackSize=256 -XX:MetaspaceSize=38m -XX:MaxMetaspaceSize=380m 分析程序的逻辑,程序会加载很多jar到内存,程序是一个公共服务,很多同事会上传jar,然后程序把jar加载到classloader进行分析并保存。

2.问题分析:

根据jdk8的metaspace的fullgc的触发条件,初始metaspacesize是38m意味着当第一次加载的class达到38m的时候进行第一次gc(根据JDK 8的特性,G1和CMS都会很好地收集Metaspace区(一般都伴随着Full GC)。),然后jvm会动态调整 (gc后会进行调整)metaspacesize的大小。

1

JDK8: Metaspace In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace. There are some new flags added for Metaspace in JDK 8: -XX:MetaspaceSize=<NNN> where <NNN> is the initial amount of space(the initial high-water-mark) allocated for class metadata (in bytes) that may induce a garbage collection to unload classes. The amount is approximate. After the high-water-mark is first reached, the next high-water-mark is managed by the garbage collector -XX:MaxMetaspaceSize=<NNN> where <NNN> is the maximum amount of space to be allocated for class metadata (in bytes). This flag can be used to limit the amount of space allocated for class metadata. This value is approximate. By default there is no limit set. -XX:MinMetaspaceFreeRatio=<NNN> where <NNN> is the minimum percentage of class metadata capacity free after a GC to avoid an increase in the amount of space (high-water-mark) allocated for class metadata that will induce a garbage collection. -XX:MaxMetaspaceFreeRatio=<NNN> where <NNN> is the maximum percentage of class metadata capacity free after a GC to avoid a reduction in the amount of space (high-water-mark) allocated for class metadata that will induce a garbage collection. By default class metadata allocation is only limited by the amount of available native memory. We can use the new option MaxMetaspaceSize to limit the amount of native memory used for the class metadata. It is analogous(类似) to MaxPermSize. A garbage collection is induced to collect the dead classloaders and classes when the class metadata usage reaches MetaspaceSize (12Mbytes on the 32bit client VM and 16Mbytes on the 32bit server VM with larger sizes on the 64bit VMs). Set MetaspaceSize to a higher value to delay the induced garbage collections. After an induced garbage collection, the class metadata usage needed to induce the next garbage collection may be increased.

根据这段描述可以知道: 1.当metadata usage reaches MetaspaceSize(默认MetaspaceSize在64为server上是20.8m)就会触发gc;

2.XX:MinMetaspaceFreeRatio是用来避免下次申请的空闲metadata大于暂时拥有的空闲metadata而触发gc,举个例子就是,当metaspacesize的使用大小达到了第一次设置的初始值6m,这时进行进行扩容(之前已经做过MinMetaspaceExpansion和MaxMetaspaceExpansion扩展,但还是失败),然后gc后,由于回收调的内存很小,然后计算((待commit内存)/(待commit内存+已经commmited内存) ==40%,(待commit内存+已经commmited内存)大于了metaspaceSize那么将尝试做扩容,也就是增大触发metaspaceGC的阈值,不过这个增量至少是MinMetaspaceExpansion才会做,不然不会增加这个阈值) ,这个参数主要是为了避免触发metaspaceGC的阈值和gc之后committed的内存的量比较接近,于是将这个阈值(metaspaceSize)进行扩大,尽量减小下次gc的几率。

3.同理-XX:MaxMetaspaceFreeRatio(默认70)是用来避免下次申请的空闲metadata很小,远远小于现在的空闲内存从而导致gc。主要作用是减小不必要的内存占用空间。 jdk8的metaspace引发的fullgc: jdk8使用metaspace代替之前的perm,metaspace使用native memory,默认情况下使用的最大大小是系统内存大小,当然也可以使用-XX:MaxMetaspaceSize设置最大大小,这个设置和之前的max perm size是一样的。同时当设置-XX:MaxMetaspaceSize这个参数后,我们也可以实现和max perm引起oom的问题。 We can achieve the famed OOM error by setting the MaxMetaspaceSize argument to JVM and running the sample program provided. metaspaceSize 默认初始大小:

1

MetaspaceSize (12Mbytes on the 32bit client VM and 16Mbytes on the 32bit server VM with larger sizes on the 64bit VMs).

可以通过-XX:MetaspaceSize 设置我们需要的初始大小,设置大点可以增加第一次达到full gc的时间。

ps:下面是调整了下参数重启的进程,和上面的进程Id有出入。

1

jstat -gc 1706 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 31744.0 32768.0 0.0 21603.6 195584.0 192805.8 761856.0 384823.3 467712.0 309814.3 65536.0 36929.1 101 2.887 3 1.224 4.112

分析:MC是已经commited的内存,MU是当前使用的内存。这里有个疑惑就是MC是不是就是metaspace已经总共使用的内存,因为这个值已经达到了maxmetaspacesize,同时为什么mu不是和mc一样我猜测是由于碎片内存导致,这里有知道的同学可以告诉我下。在达到maxmetaspacesize的时候执行了3次fullgc。但是接下来由于不断申请内存,不断fullgc,fullgc不能回收内存,这时候fullgc的频率增大很多。在接下来 top -H -p 1706查看cpu可以看到大量高cpu进程,通过jstack查看都是在进行fullgc。 jmap -clstats 1706 第一次:total = 131 8016 13892091 N/A alive=45, dead=86 N/A 第二次:total = 1345 37619 77242171 N/A alive=1170, dead=175 N/A alive的classloader基本都是自己创建的 classLoader不断增加,每次gc并没有回收掉classloader VM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload): • 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。 • 加载该类的ClassLoader已经被GC。ClassLoader被回收需要所有ClassLoader的所有类的实例都被回收。 • 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法

1

jcmd 1706 GC.class_stats | awk '{print $13}' | sort | uniq -c | sort -nrk1 > topclass.txt

class.png 通过自定义的classloader加载的类重复多次,并且数量一直增加。 看到大量的类重复数量 gc日志分析: 第一次fullgc:

1

[Heap Dump (before full gc): , 0.4032181 secs]2018-01-10T16:37:44.658+0800: 21.673: [Full GC (Metadata GC Threshold) [PSYoungGen: 14337K->0K(235520K)] [ParOldGen: 18787K->30930K(761856K)] 33125K->30930K(997376K), [Metaspace: 37827K->37827K(1083392K)], 0.1360661 secs] [Times: user=0.65 sys=0.04, real=0.14 secs]

主要是Metaspace这里:[Metaspace: 37827K->37827K(1083392K)] 达到了我们设定的初始值38m,并且gc并没有回收掉内存。1083392K这个值怀疑是使用了CompressedClassSpaceSize = 1073741824 (1024.0MB)这个导致的。 第四次fullgc:

1

[Heap Dump (before full gc): , 5.3642805 secs]2018-01-10T16:53:43.811+0800: 980.825: [Full GC (Metadata GC Threshold) [PSYoungGen: 21613K->0K(231424K)] [ParOldGen: 390439K->400478K(761856K)] 412053K->400478K(993280K), [Metaspace: 314108K->313262K(1458176K)], 1.2320834 secs] [Times: user=7.86 sys=0.06, real=1.23 secs]

主要是Metaspace这里: [Metaspace: 314108K->313262K(1458176K)]达到了我们设定的MinMetaspaceFreeRatio,并且gc几乎没有回收掉内存。1458176K这个值是CompressedClassSpaceSize = 1073741824 (1024.0MB)和 MaxMetaspaceSize = 503316480 (480.0MB)的和。 后面就是频率很快的重复fullgc。

3.问题解决:

有了以上基础,就知道怎么解决这次遇到的问题了。 总结下原因:classloader不断创建,classloader不断加载class,之前的classloader和class在fullgc的时候没有回收掉。

1. 程序避免创建重复classloader,减少创建classLoader的数量。
2. 增大XX:MinMetaspaceFreeRatio(默认40)的大小,可以看到现在是(100-67.19)。
3. 设置更大的maxmetaspaceSize。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发与安全

linux网络编程之POSIX 消息队列 和 系列函数

一、在前面介绍了system v 消息队列的相关知识,现在来稍微看看posix 消息队列。 posix消息队列的一个可能实现如下图: ? 其实消息队列就是一个可...

30700
来自专栏逆向技术

32位汇编第一讲x86和8086的区别,以及OllyDbg调试器的使用

32位汇编第一讲x86和8086的区别,以及OllyDbg调试器的使用 一丶32位(x86也称为80386)与8086(16位)汇编的区别 1.寄存器的改变  ...

34390
来自专栏Seebug漏洞平台

利用 phar 拓展 php 反序列化漏洞攻击面

通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但在不久前的Black Ha...

20350
来自专栏腾讯Bugly的专栏

Android 混淆那些事儿

本文主要讲述了代码混淆和资源混淆的原理,Studio默认的混淆方案,混淆的参数,以及如何对Apk进行代码混淆(自定义混淆文件)和资源混淆(结合微信混淆和美团混淆...

1.1K50
来自专栏牛肉圆粉不加葱

Spark Task 的执行流程② - 创建、分发 Task

task 的创建本应该放在分配 tasks 给 executors一文中进行介绍,但由于创建的过程与分发及之后的反序列化执行关系紧密,我把这一部分内容挪到了本文...

10410
来自专栏张善友的专栏

.net 2.0 你是如何使用事务处理?

     事务处理作为企业级开发必备的基础设施, .net 2.0通过System.Transactions对事务提供强大的支持.你还是在使用.net 1.x下...

22960
来自专栏用户2442861的专栏

修改npm全局安装模式的路径

刚学nodeJS不久,很纳闷为什么全局安装的模块在 'node安装目录/node_modules‘ 中没找到!后来仔细看了下安装成功后的信息,才发现原来是自动安...

11720
来自专栏技术碎碎念

OS存储器管理(一)

存储器的层次: 分为寄存器、主存(内存)和 辅存(外存)三个层次。 主存:高速缓冲存储器、主存储器、磁盘缓冲存储器,          主存又称为可执行存储...

41090
来自专栏Python、Flask、Django

python中用requests获取API参数

26260
来自专栏漫漫全栈路

ASP.NET MVC学习笔记05模型与访问数据模型

上一篇使用的M模型,并不是真正意义上的Model,现在来添加一些类,并将这些类用来管理数据库中数据(电影)。而这些类,就是ASP.NET MVC中的Model...

31640

扫码关注云+社区

领取腾讯云代金券