为何清除类与ClassLoader引用后Java类仍未被卸载?
Java类卸载问题解答
1. 类未被卸载的原因
- GC仅为建议,非强制执行:调用
System.gc()只是向JVM发送垃圾回收请求,JVM有权自主决定是否执行以及执行时机,断点处可能GC还未真正触发。 - 调试环境的隐性引用:断点调试时,IDE调试器会持有类加载器或
Class对象的额外引用,阻止其被垃圾回收,进而导致类无法卸载。 - 双亲委派机制的潜在影响:你使用的
URLClassLoader默认父加载器是系统类加载器,如果com.expirement.Loadable1已经被系统类加载器加载过,自定义加载器会直接复用父加载器的类实例,此时释放自定义加载器的引用也无法影响父加载器持有的类。 - 未完全清除所有隐性引用:虽然你将
cl和c置为null,但可能存在其他隐性引用,比如类的静态变量持有外部对象引用、线程上下文类加载器仍指向该自定义加载器等。
2. 不停止运行时销毁类的方法
- 彻底清除所有引用:
- 将自定义类加载器的所有引用(包括静态引用、线程上下文引用)显式置为null。
- 确保被加载类的所有实例已被回收,静态变量无残留引用。
- 规避双亲委派干扰:创建自定义类加载器时,指定父加载器为
null(打破双亲委派),确保类仅由该自定义加载器加载,避免父加载器持有类实例:URLClassLoader cl = new URLClassLoader(new URL[]{ Main.class.getProtectionDomain().getCodeSource().getLocation() }, null); // 指定父加载器为null - 提升GC执行概率:结合多次GC调用并短暂休眠,增加GC执行的可能性(注意仍无法保证100%触发):
cl.close(); cl = null; c = null; System.gc(); System.runFinalization(); Thread.sleep(100); System.gc(); - 脱离调试环境验证:调试器会持有额外引用,建议直接运行程序,通过
jmap -histo或jconsole等JVM工具检查类是否被卸载,而非在断点处查看。 - 启用类卸载日志与适配GC策略:启动JVM时添加
-XX:+TraceClassUnloading和-XX:+TraceClassLoading参数,可直观查看类加载/卸载日志;同时使用-XX:+UseParallelGC等GC策略,提升垃圾回收的主动性。
内容的提问来源于stack exchange,提问作者Tech of the Absence
相关产品推荐
相关产品推荐

