性能文章>一次诡异的JVM堆外内存泄漏>

一次诡异的JVM堆外内存泄漏转载

1周前
188801

导语

堆外metaspace内存占用高达3GB多 ,机器内存耗尽后导致宕机,jmap查看JVM对象信息,发现大量和反射相关对象被生成,本篇介绍的是一次诡异的jvm对外内存泄漏排查及优化过程,希望对阅读的各位有所帮助。

正文

一、现象

  • 报警详情: MEM usage above 90% (current value: 0.9731329333728482) 
  • 堆外metaspace内存占用高达3GB多 
  • 机器内存耗尽,宕机

二、概念

元空间是jdk1.8开始取代永久代的内存模型,被jvm使用受操作系统管辖的直接内存区域。

jdk1.7内存结构:

一次诡异的JVM堆外内存泄漏数据图表-heapdump性能社区


jdk1.8内存结构:

一次诡异的JVM堆外内存泄漏数据图表-heapdump性能社区


三、排查思路

  •  metaspace增长是逐渐增多,增长速度不均匀,考虑是接口调用或mq 
  •  metaspace主要存放类信息,所以主要怀疑:动态类生成类库的使用 
  •  项目中使用的第三方库:fastjson,dubbo,aspectj,spring 】 首先排除了fastjson,dubbo,spring,因为足够稳定和靠谱 
  • 但是预发环境和测试环境,依旧无法重现问题

四、排查过程

  • jmap查看JVM对象信息,发现大量和反射相关对象被生成
admin@ip-xxx:~/logs/xxx$ jmap -histo pid

 num     #instances         #bytes  class name
----------------------------------------------
   1:        836613      102185264  [C
   2:        128213       30887816  [B
   3:        546707       13120968  java.lang.String
   4:        390652       12500864  java.util.HashMap$Node
   5:         58502       10908984  [I
   6:        121161       10662168  java.lang.reflect.Method
   7:        149994       10117912  [Ljava.lang.Object;
   8:         61139        6432184  [Ljava.util.HashMap$Node;
   9:        160882        5148224  java.util.concurrent.ConcurrentHashMap$Node
  10:        172682        3822600  [Ljava.lang.Class;
  11:         58938        2829024  java.util.HashMap
  12:         24263        2688136  java.lang.Class
  13:        110260        2646240  java.util.ArrayList
  14:         65896        2635840  java.util.LinkedHashMap$Entry
  15:         41654        2252544  [Ljava.lang.String;
  16:          2459        1894392  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  17:         24752        1782144  java.lang.reflect.Field
  18:         73982        1775568  java.lang.StringBuilder
  19:         29437        1648472  java.util.LinkedHashMap
  20:         51507        1648224  java.lang.ref.WeakReference
  21:         34020        1632960  org.aspectj.weaver.reflect.ShadowMatchImpl
  22:         30742        1229680  com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl$1
  23:         76420        1222720  java.lang.Integer
  24:         34020        1088640  org.aspectj.weaver.patterns.ExposedState
  25:         12658        1012640  java.lang.reflect.Constructor
  26:         24377         975080  java.util.HashMap$KeyIterator
  27:         59090         945440  java.lang.Object
  28:         23290         931600  java.lang.ref.SoftReference
  29:         18518         919184  [Ljava.lang.reflect.Method;
  30:         35328         847872  com.dianping.cat.internal.shaded.io.netty.buffer.PoolThreadCache$MemoryRegionCache$Entry
  31:         24738         791616  com.sun.xml.internal.stream.events.CharacterEvent
  32:         13612         762272  sun.nio.cs.UTF_8$Encoder
  33:         31471         755304  io.termd.core.term.Feature
  34:         23095         739040  com.sun.org.apache.xerces.internal.xni.QName
  35:         10412         666368  java.util.regex.Matcher
  36:         11898         666288  java.beans.MethodDescriptor


●  增加jvm启动参数:-XX:+TraceClassLoading -XX:+TraceClassUnloading 
●  查看项目的类加载日志

[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_InboxBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_RefundBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_RefundLineBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_PushBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_RefundPushArgs from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_3_PushRefundLine from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_InboxBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_PushBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_PushArgs from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_RefundBO from jar:file:/home/admin/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_RefundLineBO from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_PushBO from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_RefundPushArgs from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_3_PushRefundLine from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_InboxBO from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_RefundBO from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_RefundLineBO from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_PushBO from jar:file:/home/.../-server-prod.jar!/BOOT-INF/lib/fastjson-1.2.58.jar!/]

 

xxx 03:29:32.045 [http-nio-8888-exec-7] INFO   xxx.proxy.push.PushProxy:[80] - SendMixPush, url: xxx, body: {"message":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx},"sns":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx}}
xxx 03:29:48.813 [http-nio-8888-exec-5] INFO   xxx.proxy.push.PushProxy:[80] - SendMixPush, url: xxx, body: {"message":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx},"sns":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx}}
xxx 03:29:56.844 [http-nio-8888-exec-2] INFO   xxx.proxy.push.PushProxy:[80] - SendMixPush, url: xxx, body: {"message":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx},"sns":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx}}
xxx 03:30:03.787 [http-nio-8888-exec-9] INFO   xxx.proxy.push.PushProxy:[80] - SendMixPush, url: xxx, body: {"message":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx},"sns":{"args":{"order_no":"xxx","return_id":"xxx"},"cellphone":"xxx","country":"India","request_type":"xxx","user_id":xxx}}

 

  • 查看具体代码
public static String toSneakJSONString(Object object){
      SerializeConfig serializeConfig = new SerializeConfig();
      serializeConfig.setPropertyNamingStrategy(PropertyNamingStrategy.SnakeCase);
      return JSONObject.toJSONString(object, serializeConfig, SerializerFeature.DisableCircularReferenceDetect);
  }

 

  • 《fastjson 官方建议 》https://github.com/alibaba/fastjson/issues/385#issuecomment-209281348

五、问题根源和解决方案

1.问题根源分析

SerializeConfig 默认会激活 a**,在序列化对象时会为对象生成代理类,然后通过执行代理进行序列化操作,通过这样优化来提高执行性能,但在应用不合理每次新创建 config 的时候就会导致大量生成代码类反而拖慢性能。反序列化时的 ParserConfig 也是同理。

在 jdk8 之前这些代理类会充满 Perm 区导致 FullGC,浪费点 CPU 也不会有大问题,但在 JDK8 中,这些类会大量创建直至充满物理机内存,操作系统检测到该进程是危险进程,出于自我保护机制,进而导致进程被系统杀掉。

2.解决方案

  •  代码维度,将SerializeConfig缓存起来,或者使用SerializeConfig.globalInstance全局配置,避免请求维度动态生成类并加载到元空间
  • 调整jvm启动参数,限制metaspace最大内存-XX:MaxMetaspaceSize,宁可FGC或者OOM也不要进程被杀

参考

fastjosn官方issues1 -- https://github.com/alibaba/fastjson/issues/2109

fastjson官方issues2 -- https://github.com/alibaba/fastjson/issues/385

什么是元空间Metaspace -- https://www.infoq.cn/article/Java-PERMGEN-Removed

fastjson中用到的ASM -- http://pwn4.fun/2017/02/07/JAVA-ASM%E5%AD%97%E8%8A%82%E7%A0%81%E6%A1%86%E6%9E%B6/

a**官网 -- https://a**.ow2.io/

 

更多思考

JVM的泄漏及优化是我们老生常谈的问题,更多关于JVM的知识大家可以阅读以下内容加深阅读

一次 JVM 进程退出分析

一次jvm调优实战

请先登录,再评论

暂无回复,快来写下第一个回复吧~

为你推荐

不起眼,但是足以让你有收获的JVM内存分析案例
分析 这个问题说白了,就是说有些int[]对象不知道是哪里来的,于是我拿他的例子跑了跑,好像还真有这么回事。点该 dump 文件详情,查看相关的 int[] 数组,点该对象的“被引用对象”,发现所
关于内存溢出,咱再聊点有意思的?
概述 上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白 本文其实很犹豫写不写,因为感觉没有
记一次堆外内存泄漏排查过程
本文涉及以下内容 开启NMT查看JVM内存使用情况 通过pmap命令查看进程物理内存使用情况 smaps查看进程内存地址 gdb命令dump内存块 背景最近收到运维反馈,说有项目的一个
Spring Boot引起的“堆外内存泄漏”排查及经验总结
本文来自美团技术团队,作者纪兵https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak.html 背景为了更好地实现对项目的
Netty堆外内存泄漏排查盛宴
如果使用了 Netty 堆外内存,那么可以自行监控堆外内存的使用情况,不需要借助第三方工具,我们是使用的“反射”拿到的堆外内存的情况。逐渐缩小范围,直到 Bug 被找到。当我们确认某个线程的执行带来 Bug 时,可单步执行,可二分执行,定位到某行代码之后,跟到这段代码,然后继续单步执行或者二分的方
Netty堆外内存泄漏排查盛宴
最近在做一个基于 Websocket 的长连中间件,服务端使用实现了 Socket.IO 协议(基于WebSocket协议,提供长轮询降级能力) 的 netty-socketio 框架,该框架为 Netty 实现,鉴于本人对 Netty 比较熟,并且对比同样实现了 Socket.IO 协议的其他框架
一次大量 JVM Native 内存泄露的排查分析(64M 问题)
我们有一个线上的项目,刚启动完就占用了使用 top 命令查看 RES 占用了超过 1.5G,这明显不合理,于是进行了一些分析找到了根本的原因,下面是完整的分析过程,希望对你有所帮助。会涉及到下面这些内容Linux 经典的 64M 内存问题堆内存分析、Native 内存分析的基本套路
一次诡异的JVM堆外内存泄漏
导语堆外metaspace内存占用高达3GB多 ,机器内存耗尽后导致宕机,jmap查看JVM对象信息,发现大量和反射相关对象被生成,本篇介绍的是一次诡异的jvm对外内存泄漏排查及优化过程,希望对阅读的各位有所帮助。正文一、现象报警详情: MEM usage above 90% (curre