自定义ClassLoader加载jar包触发NoClassDefFoundError问题咨询
类加载异常问题解答
为什么步骤3无法找到$B的类定义
核心原因是你创建的URLClassLoader实例clA,在类A实例化完成、foo方法执行前,内部关联的Jar包资源已经不可用:
- Java 7及以上版本的
URLClassLoader实现了AutoCloseable接口,只要调用过close()方法,就会主动释放所有持有的Jar文件句柄。这种情况下已经加载成功的类可以正常使用,但后续尝试加载未缓存的新类时,URLClassLoader无法再读取Jar包内的类文件,就会抛出加载异常。 - 另一种可能是
clA关联的Jar包在运行过程中被删除、移动或者被占用导致读取权限失效,效果和主动关闭ClassLoader完全一致。 - 你核查到构造函数和
foo方法拿到的是同一个类加载器,是因为已经加载的类A的Class对象会持有clA的强引用,clA本身不会被GC,只是它内部的资源已经被释放,无法加载新类而已。 - 构造函数中提前加载类就可以正常使用,是因为JVM加载过的类会被缓存在方法区,只要ClassLoader实例没有被回收,已加载的类可以直接调用,不需要再次读取Jar包资源。
其他解决该问题的方案
方案1:调整ClassLoader关闭时机
检查你的业务逻辑,确保clA在所有需要用到它加载的类的完整生命周期内,不会被调用close()方法,等所有相关业务逻辑执行完毕后再释放ClassLoader资源即可。
方案2:自定义类加载器提前缓存所有类字节码
如果业务逻辑要求必须提前释放Jar包资源,可以自定义类加载器,在初始化阶段就把Jar包内所有类的字节码全部读取到内存缓存,后续加载类的时候直接从内存读取,不需要再依赖Jar包的可用性,示例实现如下:
import java.io.IOException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class CachedJarClassLoader extends ClassLoader { private final Map<String, byte[]> cachedClassBytes = new HashMap<>(); public CachedJarClassLoader(URL jarUrl, ClassLoader parentClassLoader) throws IOException { super(parentClassLoader); // 初始化时一次性读取所有类字节码缓存到内存 try (JarInputStream jarIn = new JarInputStream(jarUrl.openStream())) { JarEntry entry; while ((entry = jarIn.getNextJarEntry()) != null) { String entryName = entry.getName(); if (entryName.endsWith(".class") && !entry.isDirectory()) { // 把类路径转换为全限定类名 String className = entryName.replace("/", ".").replace(".class", ""); cachedClassBytes.put(className, jarIn.readAllBytes()); } } } } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { byte[] classBytes = cachedClassBytes.get(className); if (classBytes == null) { return super.findClass(className); } return defineClass(className, classBytes, 0, classBytes.length); } }
方案3:保证Jar包可访问性
如果问题是Jar包被删除/移动导致的,只要确保Jar包在clA的整个使用周期内路径不变、读取权限正常即可解决问题。
内容的提问来源于stack exchange,提问作者pro100kryto
相关产品推荐
相关产品推荐

