专栏首页服务端技术杂谈一次永久代泄漏(perm泄漏)排查

一次永久代泄漏(perm泄漏)排查

写在前面

网上关于perm区泄露的文章比较少,特别是对于动态类加载方面问题的分析比较少,在此记录下。

perm区问题一般两种解决方案:

  • 启动时perm区问题,一般修改PermSize,MaxPermSize参数即可
  • 运行时动态生成类加载问题,这种问题比较难搞,需要关心动态加载了哪些类

周末早晨收到几台机器告警(fullgc告警)(perm大于90%告警),为快速解决问题,先把几台机器重启解决,留下了一台禁用端口保留现场进行问题分析。平时上线发版比较频繁,发版后jvm回收,如果较长时间没有发版可能会造成泄露,收到告警。

吃完早饭后登上机器进行排查。

排查过程

登入机器,查看内存使用高的进程:

top

pid:15298

既然是perm区问题,查看永久代情况:

jmap -permstat pid > 15298dump.permstat

class_loader	classes	bytes	parent_loader	alive?	type

<bootstrap>	3630	21866152	  null  	live	<internal>
0x0000000705e4df00	1	2008	0x00000006c002e450	dead	sun/reflect/DelegatingClassLoader@0x00000007c0050c30
0x00000007282e1040	4	23480	0x00000006c002e450	dead	com/facebook/swift/codec/internal/compiler/DynamicClassLoader@0x00000007c377f040

通过awk统计type类型,查看加载了哪种类型的类:

awk '{ arr[$6]+=$3 } END { for (key in arr) printf("%s\t%s\n", key, arr[key]) }' 15298dump.permstat | sort -k2,2

ava/net/URLClassLoader@0x00000007c01c1598	0
java/util/ResourceBundle$RBClassLoader@0x00000007c0337380	0
type	0
sun/reflect/DelegatingClassLoader@0x00000007c0050c30	11361872
sun/reflect/misc/MethodUtil@0x00000007c777bf60	134528
<internal>	21866152
com/alibaba/fastjson/util/ASMClassLoader@0x00000007c44de0e0	4785232
N/A	5146
sun/misc/Launcher$AppClassLoader@0x00000007c021c2d8	5423216
com/facebook/swift/codec/internal/compiler/DynamicClassLoader@0x00000007c377f040	5488288
sun/misc/Launcher$ExtClassLoader@0x00000007c01c1978	615216
org/eclipse/jetty/webapp/WebAppClassLoader@0x00000007c06e0da0	738107040

WebAppClassLoader加载最多,达到了738107040,近738m。

知道了加载的类最多,怎么分析具体加载了哪种类呢?

之前一般通过Java -verbose查看启动类加载过程。

对于运行时貌似有两种方式:

  • 基于ClassFilter写一个拦截器,dump特定的类,使用SA的jar($JAVA_HOME/lib/sa-jdi.jar)编译好类,在编译好的类目录下调用下面的命令进行dump
  • 问了(政威老师),准备试试arthas,https://alibaba.github.io/arthas/classloader.html

机器上安装好arthas-boot.jar,并启动:

java -jar arthas-boot.jar

选择进程,执行类后台导出,便后续分析:

classloader -a >> &

打开导出文件:

hash:null, BootstrapClassLoader
 [B
 [C
 [D
 [F
 [I
 [J
 [Lcom.sun.activation.registries.MimeTypeFile;
 [Lcom.sun.imageio.plugins.jpeg.DHTMarkerSegment$Htable;
 [Lcom.sun.imageio.plugins.jpeg.ImageTypeProducer;
 [Lcom.sun.imageio.plugins.jpeg.JPEGImageReader$CallBackLock$State;
 [Lcom.sun.imageio.plugins.jpeg.JPEGImageWriter$CallBackLock$State;
 [Lcom.sun.imageio.plugins.jpeg.SOFMarkerSegment$ComponentSpec;
 [Lcom.sun.imageio.plugins.jpeg.SOSMarkerSegment$ScanComponentSpec;
 [Lcom.sun.jmx.mbeanserver.ClassLoaderRepositorySupport$LoaderEntry;
 [Lcom.sun.jmx.mbeanserver.MXBeanMapping;
 [Lcom.sun.org.apache.xalan.internal.utils.FeatureManager$Feature;
 [Lcom.sun.org.apache.xalan.internal.utils.FeaturePropertyBase$State;
 [Lcom.sun.org.apache.xalan.internal.utils.XMLSecurityManager$Limit;
 [Lcom.sun.org.apache.xalan.internal.utils.XMLSecurityManager$NameMap;
 [Lcom.sun.org.apache.xalan.internal.utils.XMLSecurityManager$State;
 [Lcom.sun.org.apache.xalan.internal.utils.XMLSecurityPropertyManager$Property;
 [Lcom.sun.org.apache.xerces.internal.impl.XMLEntityManager$CharacterBuffer;
 [Lcom.sun.org.apache.xerces.internal.impl.dtd.models.CMLeaf;
 [Lcom.sun.org.apache.xerces.internal.impl.dtd.models.CMNode;
 [Lcom.sun.org.apache.xerces.internal.impl.dtd.models.CMStateSet;
 [Lcom.sun.org.apache.xerces.internal.impl.dtd.models.ContentModelValidator;
 [Lcom.sun.org.apache.xerces.internal.impl.dv.DatatypeValidator;
 [Lcom.sun.org.apache.xerces.internal.impl.dv.XSSimpleType;
 [Lcom.sun.org.apache.xerces.internal.impl.dv.xs.AbstractDateTimeDV$DateTimeData;
 [Lcom.sun.org.apache.xerces.internal.impl.dv.xs.TypeValidator;
 [Lcom.sun.org.apache.xerces.internal.impl.dv.xs.XSSimpleTypeDecl;
 [Lcom.sun.org.apache.xerces.internal.impl.xpath.regex.RegularExpression;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.SchemaGrammar;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.SubstitutionGroupHandler$OneSubGroup;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSAnnotationImpl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSAttributeUseImpl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSComplexTypeDecl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSElementDecl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSGroupDecl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSNotationDecl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.XSParticleDecl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.identity.IdentityConstraint;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.identity.XPathMatcher;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.models.XSCMLeaf;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.models.XSCMValidator;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.opti.DefaultNode;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.opti.NodeImpl;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.traversers.OneAttr;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.traversers.XSDocumentInfo;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.util.SimpleLocator;
 [Lcom.sun.org.apache.xerces.internal.impl.xs.util.XInt;
 [Lcom.sun.org.apache.xerces.internal.util.Status;
 [Lcom.sun.org.apache.xerces.internal.util.SymbolHash$Entry;
 [Lcom.sun.org.apache.xerces.internal.util.SymbolTable$Entry;
 [Lcom.sun.org.apache.xerces.internal.util.XMLAttributesImpl$Attribute;
 [Lcom.sun.org.apache.xerces.internal.utils.XMLSecurityManager$Limit;
 [Lcom.sun.org.apache.xerces.internal.utils.XMLSecurityManager$NameMap;
 [Lcom.sun.org.apache.xerces.internal.utils.XMLSecurityManager$State;
 [Lcom.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager$Property;
 [Lcom.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager$State;
 [Lcom.sun.org.apache.xerces.internal.xni.QName;
 [Lcom.sun.org.apache.xerces.internal.xni.XMLLocator;
 [Lcom.sun.org.apache.xerces.internal.xni.grammars.Grammar;
 [Lcom.sun.org.apache.xerces.internal.xni.grammars.XSGrammar;
 [Lcom.sun.org.apache.xerces.internal.xs.ShortList;
 [Lcom.sun.org.apache.xerces.internal.xs.XSAnnotation;
 [Lcom.sun.org.apache.xerces.internal.xs.XSAttributeUse;
 [Lcom.sun.org.apache.xerces.internal.xs.XSComplexTypeDefinition;
 [Lcom.sun.org.apache.xerces.internal.xs.XSElementDeclaration;
 [Lcom.sun.org.apache.xerces.internal.xs.XSIDCDefinition;
 [Lcom.sun.org.apache.xerces.internal.xs.XSModelGroupDefinition;
 [Lcom.sun.org.apache.xerces.internal.xs.XSNamespaceItem;
 [Lcom.sun.org.apache.xerces.internal.xs.XSNotationDeclaration;
 [Lcom.sun.org.apache.xerces.internal.xs.XSObject;
 [Lcom.sun.org.apache.xerces.internal.xs.XSParticle;
 [Lcom.sun.org.apache.xerces.internal.xs.XSSimpleTypeDefinition;
 [Lcom.sun.org.apache.xerces.internal.xs.XSTerm;
 [Lcom.sun.org.apache.xerces.internal.xs.XSTypeDefinition;
 [Lcom.sun.org.apache.xerces.internal.xs.datatypes.XSDateTime;
 [Lcom.sun.org.apache.xml.internal.dtm.DTM;
 [Lcom.sun.org.apache.xml.internal.dtm.DTMAxisTraverser;
 [Lcom.sun.org.apache.xml.internal.dtm.DTMIterator;
 [Lcom.sun.org.apache.xml.internal.dtm.ref.ExpandedNameTable$HashEntry;
 [Lcom.sun.org.apache.xml.internal.dtm.ref.ExtendedType;
 [Lcom.sun.org.apache.xpath.internal.Expression;
 [Lcom.sun.org.apache.xpath.internal.ExpressionNode;
 [Lcom.sun.org.apache.xpath.internal.XPathVisitable;
 [Lcom.sun.org.apache.xpath.internal.axes.LocPathIterator;
 [Lcom.sun.org.apache.xpath.internal.axes.PathComponent;
 [Lcom.sun.org.apache.xpath.internal.axes.PredicatedNodeTest;
 [Lcom.sun.org.apache.xpath.internal.axes.SubContextList;
 [Lcom.sun.org.apache.xpath.internal.objects.XObject;
 [Lcom.sun.org.apache.xpath.internal.patterns.NodeTest;
 [Lcom.sun.xml.internal.ws.org.objectweb.asm.Item;
 [Lcom.sun.xml.internal.ws.org.objectweb.asm.Type;
 [Ljava.awt.AWTKeyStroke;
 [Ljava.awt.Dimension;
 [Ljava.awt.Queue;
 [Ljava.awt.event.ActionListener;
 [Ljava.awt.event.ComponentListener;
 [Ljava.awt.event.FocusListener;
 [Ljava.awt.event.HierarchyBoundsListener;
 ......

统计最多的类:

#!/usr/bin/python

from collections import Counter

package_name_count_dic = {}
for s in open('14.txt'):
    full_qualified_name = s.strip()
    if not full_qualified_name:
        continue
    class_name_index = full_qualified_name.rfind('.')
    if class_name_index >= 0:
        package_name, class_name = full_qualified_name[:class_name_index], full_qualified_name[class_name_index + 1:]
    else:
        package_name = class_name = full_qualified_name
    if package_name in package_name_count_dic:
        package_name_count_dic[package_name] += 1
    else:
        package_name_count_dic[package_name] = 1
k = Counter(package_name_count_dic)
high = k.most_common(5)
for p, c in high:
    print p, c

最多的几个类:

真凶:ma.glasnost.orika.generated。

查看代码中谁使用了orika类库。

发现是闪购同学,由于我们的系统目前对接多方,闪购同学还在我们系统做代码开发,在codereview上存在一些问题。

@Component
public class SGMapperFactory implements FactoryBean<MapperFactory> {
    @Override
    public MapperFactory getObject()  {
        return new DefaultMapperFactory.Builder().build();
    }

    @Override
    public Class<?> getObjectType() {
        return MapperFactory.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

每次调用getObject都会新创建DefaultMapperFactory对象。MapperGenerator 每次会动态产生类。

至此问题排查,推动闪购同学改动。

后记

看代码是3月份代码写的,为何到现在才发现问题呢?

  1. 平时发版比较频繁,jvm发版后问题解决。
  2. 最近发版比较少,同时这些是扩容机器,在最近的几次发版中并没有发版,所以造成类加载持续一段时间,最后造成永久代泄露。
  3. 为尽早发现问题需要在流程上进行控制,比如增加codereview细致程度,在灰度发版后对发版机器进行引流压测,尽早发现问题,解决问题。

本文分享自微信公众号 - 春哥叨叨(chungedaodao)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-14

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 上线后发现自己写了个Bug,一通操作后终于解决了

    我这边有一个系统,提供一个RPC接口去发送短信。外部调用我的接口需要传入手机号等等参数,我这边负责解析这些参数、做一些业务的处理,然后调用短信渠道商的接口发送短...

    乔戈里
  • 基于SpringCloud的Microservices架构实战案例-在线API管理

    前端和后端的唯一联系,变成了API接口;API文档变成了前后端开发人员联系的纽带,变得越来越重要,swagger就是一款让你更好的书写API文档的框架。 本实战...

    歪脖贰点零
  • 不要再问我Java程序是怎么执行的了!

    可以看到,Java虚拟机是在原有计算机的基础上虚拟出来的抽象计算机,提供Java程序的运行环境。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需要...

    walking在cloud.tencent
  • 阿里P7面试经历JAVA总结,技术面,HR面(附整理好的答案分享)

    总体上来看,还是比较注重基础的 尤其是java的多线程和并发安全性及数据库相关,另外对有关开源框架的具体底层实现需要多阅读源码并进行总结。

    Java小朔哥
  • 使用Python和Django进行Web开发的20个优点

    Python是最常用的编程语言之一,这已经不是什么秘密了。在过去的五年中,Python一直是“ 最受欢迎的编码语言”。

    用户4962466
  • Win7 Eclipse 搭建spark java1.8(lambda)环境:WordCount helloworld例子

    Win7 Eclipse 搭建spark java1.8(lambda)环境:WordCount helloworld例子 lambda表达式是java8给我...

    马克java社区
  • Java8新特性:方法引用详解

    Java8 引入了方法引用特性,使用它可以简化 Lambda 表达式,我们知道 Lambda 是用来替代匿名类的,即使用 Lambda 可以更加方便地实现函数接...

    南风
  • Arrays.asList()使用指南

    最近使用Arrays.asList()遇到了一些坑,然后在网上看到这篇文章:http://javadevnotes.com/java-array-to-list...

    乔戈里
  • 聊聊spring cloud openfeign的Targeter

    spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/open...

    codecraft
  • 《javascript数据结构和算法》读书笔记(5):集合

    在创建时有一个细节,使用对象(items)而不是数组来创建集合。但使用数组的话意义似乎不大。

    一粒小麦

扫码关注云+社区

领取腾讯云代金券