083、动手实验:Metaspace区域内存溢出的时候,应该如何解决?

Metaspace区域内存溢出的时候,应该如何解决?


更多博客内容访问火龙蛋博客_只爱吃火龙果的火龙蛋,点击了解详情


1、前文回顾

上一讲已经说了我们处理OOM需要的一些参数,今天我们来讲一下Metaspace区域内存溢出

我们先分析一下GC日志,然后再让JVM自动dump出来内存快照,最后用MAT来分析一下这份内存快照,从内存快照里去找到内存溢出的原因。

2、示例代码

首先我们先上之前的那段代码:
在这里插入图片描述

我们还是用这段代码来说明,但是要记得需要在JVM参数中加入一些东西,因为我们想看一下GC日志和导出内存快照,如下所示:

-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:MetaspaceSize=10m
-XX:MaxMetaspaceSize=10m
-XX:+PrintGCDetails
-Xloggc:gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./

大家要注意,上面那个HeapDumpPath参数我给调整为当前项目的根目录下了,这样我们看的时候方便一些。

3、分析GC日志

接着我们用上述JVM参数运行这段程序,会发现项目下面多了两个文件,一个是gc.log,还有一个是java_pid910.hprof

当然不同的机器运行这个hprof文件的名字是不太一样的,因为他会用你的PID进程id作为文件名字。

接着我们先来分析一下gc.log,也就是分析一下他是如何拼命往Metaspace区域里放入大量生成的类,然后触发Full GC,接着回收Metaspace区域,回收后还是无法放下更多的类,接着才会抛出内存溢出的异常。

然后我们再用MAT分析一下OOM的时候的内存快照,带着大家学习一下如何用MAT工具找到Metaspace内存溢出问题的原因。

先把我这里的GC日志给大家抛出来,同时我们就跟着GC日志一行一行分析,到底是怎么回事,大家紧紧跟着脚步来走。

0.716: [GC (Allocation Failure) 0.717: [ParNew: 139776K->2677K(157248K), 0.0038770 secs] 139776K->2677K(506816K), 0.0041376 secs] [Times: user=0.03 sys=0.01, real=0.00 secs]

大家看这行日志,这是第一次GC,他本身是一个Allocation Failure的问题

也就是说,他是在Eden区中分配对象时,发现Eden区内存不足了,于是就触发了一次ygc。

那么,这个对象到底是什么对象?

简单,还记得我们在代码里写的么?Enhancer本身是一个对象,他是用来生成类的,如下所示:Enhancer enhancer = new Enhancer()。

接着我们基于每次Enhancer生成的类还会生成那个类的对象,如下所示:Car car = (Car) enhancer.create()。

因此上述代码不光是动态生成类,本身他也是对应很多对象的,因此你在while(true)循环里不停的创建对象,当然会塞满Eden区了,大家看上述日志:

[ParNew: 139776K->2677K(157248K), 0.0038770 secs]

这就是说,在默认的内存分配策略下,年轻代一共可用空间是150MB左右,这里还包含了一点Survivor区域的大小

然后大概都用到140MB了,也就是Eden区都塞满了,此时就触发了Allocation Failure,没Eden区的空间分配对象了,此时就触发ygc。

这个倒没什么可说的,因为之前我们都讲过了。

0.771: [Full GC (Metadata GC Threshold) 0.771: [CMS: 0K->2161K(349568K), 0.0721349 secs] 20290K->2161K(506816K), [Metaspace: 9201K->9201K(1058816K)], 0.0722612 secs] [Times: user=0.12 sys=0.03, real=0.08 secs]

接着我们来看这次GC,这就是Full GC了,而且通过“Metadata GC Threshold”清晰看到,是Metaspace区域满了,所以触发了Full GC

这个时候看下面的日志,20290K->2161K(506816K),这个就是说堆内存(年轻代+老年代)一共是500MB左右,然后有20MB左右的内存被使用了,这个必然是年轻代用的。

然后Full GC必然会带着一次Young GC,因此这次Full GC其实是执行了ygc了,所以回收了很多对象,剩下了2161KB的对象,这个大概就是JVM的一些内置对象了。

然后直接就把这些对象放入老年代,为什么呢,因为下面的日志:[CMS: 0K->2161K(349568K), 0.0721349 secs]

这里明显说了,Full GC带着CMS进行了老年代的Old GC,结果人家本来是0KB,什么都没有,然后从年轻代转移来了2161KB的对象,所以老年代变成2161KB了。

接着看日志: [Metaspace: 9201K->9201K(1058816K)]

此时Metaspace区域已经使用了差不多9MB左右的内存了,此时明显是发现离我们限制的10MB内存很接近了,所以触发了Full GC,但是对Metaspace GC后发现类全部存活了,因此还是剩余9MB左右的类在Metaspace里。

0.843: [Full GC (Last ditch collection) 0.843: [CMS: 2161K->1217K(349568K), 0.0164047 secs] 2161K->1217K(506944K), [Metaspace: 9201K->9201K(1058816K)], 0.0165055 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]

接着又是这次Full GC,人家也说的很清晰了,Last ditch collection

就是说,最后一次拯救的机会了,因为之前Metaspace回收了一次但是没有类可以回收,所以新的类无法放入Metaspace了。

所以再最后试一试Full GC,能不能回收掉一些

结果如下:[Metaspace: 9201K->9201K(1058816K)], 0.0165055 secs]

Metaspace区域还是无法回收掉任何的类,几乎还是占满了我们设置的10MB左右。

0.860: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1217K(349568K)] 1217K(506944K), 0.0002251 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.860: [CMS-concurrent-mark-start]
0.878: [CMS-concurrent-mark: 0.003/0.018 secs] [Times: user=0.05 sys=0.01, real=0.02 secs]
0.878: [CMS-concurrent-preclean-start]
Heap
par new generation total 157376K, used 6183K [0x00000005ffe00000, 0x000000060a8c0000, 0x0000000643790000)
eden space 139904K, 4% used [0x00000005ffe00000, 0x0000000600409d48, 0x00000006086a0000)
from space 17472K, 0% used [0x00000006086a0000, 0x00000006086a0000, 0x00000006097b0000)
to space 17472K, 0% used [0x00000006097b0000, 0x00000006097b0000, 0x000000060a8c0000)
concurrent mark-sweep generation total 349568K, used 1217K [0x0000000643790000, 0x0000000658cf0000, 0x00000007ffe00000)
Metaspace used 9229K, capacity 10146K, committed 10240K, reserved 1058816K
class space used 794K, capacity 841K, committed 896K, reserved 1048576K

接着就直接JVM退出了,退出的时候就打印出了当前内存的一个情况,年轻代和老年代几乎没占用,但是Metaspace的capacity是10MB,使用了9MB左右,无法再继续使用了,所以触发了内存溢出。

此时就会在控制台打印出如下的一行东西:

Caused by: java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
… 11 more

明确抛出异常,说OutOfMemoryError,原因就是Metaspace区域满了导致的。

因此假设是Metaspace内存溢出了,然后客服通知了我们,或者我们自己监控到了异常,此时直接去线上机器看一下GC日志和异常信息就可以了,通过上述分析立刻就知道了,系统是如何运行的,触发了几次GC之后引发了内存溢出。

4、分析内存快照

当我们知道是Metaspace引发的内存溢出之后,立马就可以把内存快照文件从线上机器拷回本地笔记本电脑,打开MAT工具进行分析,如下图所示:
在这里插入图片描述

从这里可以看到实例最多的就是AppClassLoader

为啥有这么多的ClassLoader呢?一看就是CGLIB之类的东西在动态生成类的时候搞出来的,我们可以点击上图的Details进去看看。

在这里插入图片描述

为什么这里有一大堆咱们自己的Demo1中动态生成出来的Car$$EnhancerByCGLIB的类呢?

看到这里就真相大白了,上图已经清晰告诉我们,是我们自己的哪个类里搞出来了一大堆的动态生成的类,所以填满了Metaspace区域。

所以此时直接去代码里排查动态生成类的代码即可。

解决这个问题的办法也很简单,直接对Enhancer做一个缓存,只有一个,不要无限制的去生成类就可以了。

5、本文总结

今天这篇文章,带着大家全程基于示例代码从GC日志到内存快照进行了一通分析

从GC日志我们知道系统是如何在多次GC之后无奈内存溢出的

从内存快照我们就知道到底是什么东西占据了太多的内存,然后代码里找到原因解决即可。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值