调试引发Java MetaSpace内存泄漏原因及ClassLoader卸载方案咨询
自定义ClassLoader调试时的JNI引用与卸载问题
问题背景
调试用户自定义ClassLoader加载的类时,该类会被JNI全局引用,导致ClassLoader无法被卸载,多次重复操作最终会引发MetaSpace溢出。
具体疑问
TenantClassLoader被JNI全局引用(关联类为logwire.products.crm.action.customer.ChangeCustomerOwnerUserAction),请问为何ClassLoader会被类的JNI全局引用?如何才能卸载该ClassLoader?
复现情况
尝试在重建TenantClassLoader时打断点调试,但旧的TenantClassLoader仍无法卸载。已准备最小复现示例,但未完全还原线上场景:
- 复现步骤:调试源码时,若不在
MyTask的run方法中添加断点,ClassLoader可正常被垃圾回收,控制台输出‘ClassLoader count: 0’;若添加断点,程序运行后ClassLoader无法被回收。
复现代码
package test; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import java.security.ProtectionDomain; import java.time.LocalDateTime; public class TestClassLoaderLeaks { private static WeakReference<ClassLoader> classLoaderRef = null; private static void createClassLoader() throws Exception { ClassLoader classLoader = new MyClassLoader(new URL[]{}); classLoaderRef = new WeakReference<>(classLoader); Class<?> clazz = Class.forName(MyTask.class.getName(), true, classLoader); Constructor<?> constructor = clazz.getConstructor(); Runnable runnable = (Runnable) constructor.newInstance(); runnable.run(); } public static void main(String[] args) throws Exception { createClassLoader(); for (int i = 0; i < 10; i++) { gcAndPrintClassLoaderCount(500); } } private static void gcAndPrintClassLoaderCount(long sleepTime) throws InterruptedException { System.gc(); Thread.sleep(sleepTime); ClassLoader classLoader = classLoaderRef.get(); LocalDateTime now = LocalDateTime.now(); System.out.println(now + ": ClassLoader : " + classLoader); } public static class MyClassLoader extends URLClassLoader { private Class<?> clazz; public MyClassLoader(URL[] urls) { super(urls); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.equals(MyTask.class.getName())) { if (clazz == null) { String className = MyTask.class.getName(); String classPath = className.replace(".", "/") + ".class"; ClassLoader classLoader = TestClassLoaderLeaks.class.getClassLoader(); try (InputStream resourceAsStream = classLoader.getResourceAsStream(classPath)) { if (resourceAsStream == null) { throw new FileNotFoundException("File " + classPath + " not found"); } ProtectionDomain protectionDomain = new ProtectionDomain(null, null); byte[] bytes = resourceAsStream.readAllBytes(); clazz = defineClass(className, bytes, 0, bytes.length, protectionDomain); } catch (IOException e) { throw new RuntimeException(e); } } return clazz; } else { return super.loadClass(name, resolve); } } } public static class MyTask implements Runnable { @Override public void run() { MyClassLoader classLoader = (MyClassLoader) this.getClass().getClassLoader(); System.out.println("This is ClassLoader: " + classLoader); } } }
环境信息
- JDK版本:openjdk version "17.0.10" 2024-01-16,OpenJDK Runtime Environment Temurin-17.0.10+7,OpenJDK 64-Bit Server VM Temurin-17.0.10+7(混合模式)
- 使用场景:需频繁热加载开发者编写的Java代码,实现无需重启即可快速迭代
- 问题触发条件:仅在调试时出现,非调试场景下多次创建加载ClassLoader无泄漏情况
- 调试工具:IntelliJ IDEA 2024.1
问题原因
调试时ClassLoader被JNI全局引用的核心原因是调试器的底层实现逻辑:
- 当在类的方法上设置断点时,IDEA这类调试器会通过JNI接口将目标类的元数据(包括
Class对象、关联的ClassLoader信息)注册为全局引用,目的是在断点暂停时能稳定获取栈帧、局部变量等调试数据。这些JNI全局引用不会被JVM自动回收,除非调试会话结束或手动清理相关调试上下文。 Class对象本身持有其ClassLoader的强引用,一旦Class对象被JNI全局引用持有,ClassLoader就会因为存在完整的强引用链而无法被垃圾回收。
解决方案
1. 调试后主动清理调试资源
- 调试结束后直接关闭调试会话(而非仅暂停程序),IDEA会自动释放对应的JNI全局引用,此时触发GC即可回收ClassLoader。
- 若需持续调试,及时移除不再需要的断点,避免无用的调试上下文占用JNI引用。
2. 调整调试方式
- 对于需要频繁热加载的类,尽量不在其方法内设置断点,改用日志打印、远程诊断等非侵入式方式排查问题。
- 若必须断点调试,完成后立即清理断点,并手动触发Full GC(可通过
jmap -histo、JConsole等工具操作)。
3. 优化ClassLoader实现
- 确保自定义ClassLoader没有被其他强引用持有:比如避免将ClassLoader实例存在静态变量、ThreadLocal中,调试时也要注意不要让调试器的变量视图长时间保留ClassLoader引用。
- 继承
URLClassLoader时,在不再使用时调用close()方法(JDK 1.7+支持),释放内部资源,帮助GC更快识别可回收对象。
4. 调整JVM参数(可选)
- 对于JDK 17,可尝试添加参数
-XX:+UseZGC或-XX:+UseShenandoahGC,这些低延迟GC对弱引用和JNI引用的处理更高效,能更快回收未被使用的ClassLoader。 - 关闭IDEA的"Auto reload classes"功能,避免调试器频繁关联类元数据。
内容的提问来源于stack exchange,提问作者kyle wang
相关产品推荐
相关产品推荐

