美文网首页
应用 "内存泄漏"排查

应用 "内存泄漏"排查

作者: 内沐 | 来源:发表于2020-05-27 20:15 被阅读0次

现象:应用启动后,容器内存,jvm内存 持续增加。见图


image.png

图1 容器内存使用图


image.png
图2 应用启动时jvm堆内存使用图
image.png

图3 应用运行了一段时间后jvm堆内存使用图

图3比图2的堆内存高出了2G, 说明Full GC 执行后,部分对象仍然存在,Full GC 销毁不彻底。

通过上面可以判断出,系统大概率是出现了内存泄漏。

于是开始排查。拿到不同时段的jvm的dump文件。

image.png

首先使用jvm自带的内存分析工具 jvisualvm,该工具在jvm bin目录下。

image.png

将dump文件装入 jvisualvm

先装入 jmap_325749_2020-05-27-08-44-17

image

可以看到 hashMap$Entry实例数最多。

再装入 jmap_325749_2020-05-27-09-38-47

image

结果是一样的,都是hashMap$Entry比较多,将两个dump文件对比下,结果见下图

image

也就是说随着时间的增加。这个对象的实例在增加。可以说明这个对象有很大可能存在问题,需要对其进行深度排查。

但这个工具在更深层次的信息展示上用户友好度一般。于是找到了Jprofiler. 这个之前也有同事提到及使用过。缺点是这个是收费的。但有10天免费的使用机会。

官网:https://www.ej-technologies.com/

安装过程主要为以下几步:

  1. 在idea 插件中心安装这个插件。
  2. 在官网下载客户端,安装在本地
  3. 与idea集成

将上面的dump文件后缀改为 *.hprof,直接用Jprofiler打开即可。如图:

image

可以看到也是hashMap$Entry最多

点击 Biggest Objects

image

通过级联关系可以看到造成实例增加的就是com.jd.fms.plough.utils.XmlUtil 了,于是开始进入代码分析阶段。

这是一个工具类,用来解析xml的。代码如下


import com.thoughtworks.xstream.XStream;
 
public class XmlUtil {
    private static XStream xs = new XStream();

    @SuppressWarnings("unchecked")
    public static <T> T toObjectIgnoreElementsForCDMessage(String xml, Class<?> clazz) throws Exception {
        if (xs == null) {
            xs = new XStream();
        }

        //第21行
        xs.denyTypes(new String[]{"org.apache.commons.configuration.ConfigurationMap",
                "org.apache.commons.logging.impl.NoOpLog",
                "org.apache.commons.configuration.Configuration",
                "org.apache.commons.configuration.JNDIConfiguration",
                "java.util.ServiceLoader$LazyIterator",
                "com.sun.jndi.rmi.registry.BindingEnumeration",
                "org.apache.commons.beanutils.BeanComparator",
                "jdk.nashorn.internal.objects.NativeString",
                "com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data",
                "sun.misc.Service$LazyIterator", "com.sun.jndi.rmi.registry.ReferenceWrapper",
                "com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl",
                "org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder",
                "org.springframework.beans.factory.BeanFactory", "org.springframework.jndi.support.SimpleJndiBeanFactory",
                "org.springframework.beans.factory.support.RootBeanDefinition",
                "org.springframework.beans.factory.support.DefaultListableBeanFactory",
                "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
                "org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory",
                "org.springframework.aop.aspectj.AspectJPointcutAdvisor",
                "org.springframework.aop.aspectj.AspectJAroundAdvice",
                "org.springframework.aop.aspectj.AspectInstanceFactory",
                "org.springframework.aop.aspectj.AbstractAspectJAdvice",
                "javax.script.ScriptEngineFactory",
                "com.sun.rowset.JdbcRowSetImpl",
                "com.rometools.rome.feed.impl.ToStringBean",
                "com.rometools.rome.feed.impl.EqualsBean",
                "java.beans.EventHandler",
                "javax.imageio.ImageIO$ContainsFilter",
                "java.util.Collections$EmptyIterator",
                "javax.imageio.spi.FilterIterator",
                "java.lang.ProcessBuilder",
                "org.codehaus.groovy.runtime.MethodClosure",
                "groovy.util.Expando",
                "com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource"});

        xs.processAnnotations(clazz);
        xs.ignoreUnknownElements();
        return (T) xs.fromXML(xml);
    }

}

第21行 有个 xs.denyTypes()方法,用来向一个list集合中增加一些对象,如果在这个集合中则拒绝提供服务。也就是类似黑名单的功能。

这个方法干了什么?

public void denyTypes(String[] names) {
        denyPermission(new ExplicitTypePermission(names));
}

继续跟:

  public void denyPermission(TypePermission permission) {
        addPermission(new NoPermission(permission));
    }
public void addPermission(TypePermission permission) {
        if (securityMapper != null) {
            insecureWarning &= permission != NoTypePermission.NONE;
            securityMapper.addPermission(permission);
        }
    }

    public void addPermission(final TypePermission permission) {
        if (permission.equals(NoTypePermission.NONE) || permission.equals(AnyTypePermission.ANY))
            permissions.clear();
        permissions.add(0, permission);
    }

可以看到最后都加到了 Permissions 这个集合对象中。

Permissions 是什么?

image

Permissions是一个list集合,是 xstream 对象属性的属性。

在XmlUtil中有这样的定义:

private static XStream xs = new XStream();

看到这里各位大神肯定都知道了。

XStream 是一个 static 变量,其生命周期为:在类装载的时候被装载到内存,不自动进行销毁,会一直存在于内存中,直到JVM关闭。所以这个集合就会持续增大。

通过单元测试看一下:


image

看到这里,优化方案也就出来了,既然一次就能做完的事那就不需要重复做了。调整如下:就是将调用这个方法的逻辑放到上面的 if中。这样只会初始化一次。

看下优化的效果:

image

优化前堆内存图

image

优化后堆内存图

可以看到优化后,堆内存不会在持续增长。

image

Jprofiler 全览图

至此优化结束。

总结:编码时在用到集合、静态变量时要考虑下对象是否会长期存在。

相关文章

网友评论

      本文标题:应用 "内存泄漏"排查

      本文链接:https://www.haomeiwen.com/subject/wwpgahtx.html