现象:应用启动后,容器内存,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/
安装过程主要为以下几步:
- 在idea 插件中心安装这个插件。
- 在官网下载客户端,安装在本地
- 与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 是什么?
imagePermissions是一个list集合,是 xstream 对象属性的属性。
在XmlUtil中有这样的定义:
private static XStream xs = new XStream();
看到这里各位大神肯定都知道了。
XStream 是一个 static 变量,其生命周期为:在类装载的时候被装载到内存,不自动进行销毁,会一直存在于内存中,直到JVM关闭。所以这个集合就会持续增大。
通过单元测试看一下:
image
看到这里,优化方案也就出来了,既然一次就能做完的事那就不需要重复做了。调整如下:就是将调用这个方法的逻辑放到上面的 if中。这样只会初始化一次。
看下优化的效果:
image优化前堆内存图
image优化后堆内存图
可以看到优化后,堆内存不会在持续增长。
imageJprofiler 全览图
至此优化结束。
总结:编码时在用到集合、静态变量时要考虑下对象是否会长期存在。
网友评论