记录一次Metaspace内存泄露
问题现象:java程序设置了-Xmx2048m,top命令查看进程实际占用内存达到了8GB,远超设置的堆内存最大值2048m,程序运行正常无明显异常。
定位过程
参考:java内存泄漏分析
1.先确认内存泄露的类型
思路:因程序可正常使用,无明显异常,大概可以排除程序堆内存泄露,如果是堆内泄露日志应该报 java.lang.OutOfMemoryError: Java heap space且程序不应该可以继续使用。
确认下我们的猜想,使用 jcmd ${pid} VM.native_memory summary scale=MB命令查看下NMT数据
从返回的信息中看出Java Heap使用了2048MB,并未超出-Xmx2048m,但是Class 使用了4888MB,从这儿看出明显是Metaspace发生了内存泄漏。
2.设置-XX:MaxMetaspaceSize
jmap -heap ${pid} 查看下Heap Configuration,从图中能看到java程序的 MaxMetaspaceSize = 17592186044415 MB,我们知道java元空间在不设置 -XX:MaxMetaspaceSize参数的情况下,是受限于本地内存的大小的,所以发生上面的Class 使用了4888MB的情况。
加上-XX:MaxMetaspaceSize=512m 再看看情况。
3.定位问题代码
加上-XX:MaxMetaspaceSize=512m 后发现进程内存控制住了,但是又有新的问题,发生了java.lang.OutOfMemoryError: Metaspace(见下图)。
那么就不仅仅是Metaspace没回收的问题了,方法区中有无法回收的东西占了内存。方法区的回收主要分为两部分:废弃变量和无用的类。上面看到是Class 使用了4888MB,那先看下进程中加载了哪些类。
JVM参数加上 -XX:+TraceClassLoading -XX:+TraceClassUnloading 查看进程加载类信息,发现程序一直在重复加载mysql-connector-java.jar中的类
使用arthas查看下类的加载路径
再查看下代码发现代码中每次都初始化新的classLoader去动态加载本地jar包
DataMgrClassLoader classLoader = new DataMgrClassLoader(urls.toArray(new URL[] {}), Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
Class driver = null;
try {
driver = classLoader.loadClass(context.getParams().get(AgentConsts.JobParams.DBDRIVERNAME));
} catch (ClassNotFoundException e) {
logger.error("classloader loadclass failed...");
}
logger.info("driver Class: {}", driver);
return null;
调整该段代码,复用classLoader,再做测试,测试通过。
- 点赞
- 收藏
- 关注作者
评论(0)