自定义类加载器替换系统类加载器时找不到com.sun.tools.javac.Main类
我尝试用自定义类加载器MyCL替换默认系统类加载器,执行了以下两条命令(setCustomCL是我的应用JAR包):
- i)
javac -J-Djava.class.path=/Users/gouravkumar/Desktop/Keploy/KPOCs/Class_Isolation_Loading/setCustomCL/target/setCustomCL-1.0-SNAPSHOT.jar -J-Djava.system.class.loader=org.example.ccl.MyCL - ii)
javac -J-Djava.class.path=/Users/gouravkumar/Desktop/Keploy/KPOCs/Class_Isolation_Loading/setCustomCL/target/setCustomCL-1.0-SNAPSHOT.jar -Xbootclasspath/p:/Users/gouravkumar/Library/Java/JavaVirtualMachines/corretto-1.8.0_342/Contents/Home/lib/tools.jar -J-Djava.system.class.loader=org.example.ccl.MyCL
自定义类加载器MyCL的代码如下:
package org.example.ccl; import org.jetbrains.annotations.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class MyCL extends ClassLoader { private static final String CLASS_FILE_EXTENSION = ".class"; public MyCL(ClassLoader parent) { super(parent); System.out.println("[MyCL classloader] Constructor:" + parent); } public MyCL() { System.out.println("[MyCL classloader] Default Constructor"); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // Check if the class is already loaded System.out.println("[MyCL classloader] loading class: " + name); Class<?> clazz = findLoadedClass(name); if (clazz != null) { System.out.println("[MyCL classloader] class already loaded:" + clazz); return clazz; } // Delegate to the parent classloader for bootstrap and extension classes if (isBootstrapClass(name) || isExtensionClass(name)) { System.out.println("[MyCL classloader] {" + name + "} should be loaded by boostrap or extension classloader"); return super.loadClass(name, resolve); } // Try to load the class with this classloader byte[] classData = loadClassData(name); clazz = defineClass(name, classData, 0, classData.length); if (resolve) { resolveClass(clazz); } System.out.println("[MyCL classloader] succesfully loaded:" + clazz); return clazz; } private boolean isBootstrapClass(String name) { String[] bootstrapPrefixes = { "java.", "java/", "javax.", "javax/", "sun.", "sun/", "com.sun.", "com/sun/"}; for (String prefix : bootstrapPrefixes) { if (name.startsWith(prefix)) { return true; } } return false; } private boolean isExtensionClass(String name) { String[] extensionPrefixes = { "com.sun.javafx.", "com.oracle.", "jdk.", "oracle.", "netscape.", "org.ietf.jgss.", "sunw.", "org.omg.", "org.w3c.dom.", "org.xml.sax.", "org.jcp.xml.dsig.internal.", "com.sun.xml.internal.", "com.sun.activation.", "com.sun.imageio.", "com.sun.jmx.", "com.sun.management.", "com.sun.org.apache.", "com.sun.security.", "com.sun.tools.", "jdk.internal.", "oracle.net.", "sun.awt.", "sun.misc.", "sun.net.", "sun.security.", "sun.text.", "sun.util." }; for (String prefix : extensionPrefixes) { if (name.startsWith(prefix)) { return true; } } return false; } private byte[] loadClassData(String className) { String classFileName = className.replace('.', '/') + CLASS_FILE_EXTENSION; System.out.println("[MyCL classloader] trying to get class data of class:" + classFileName); InputStream inputStream = getResourceAsStream(classFileName); if (inputStream == null) { System.out.println("[MyCL classloader] cannot get class data as inputstream is null."); return null; } try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { int bytesRead; byte[] buffer = new byte[4096]; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } return outputStream.toByteArray(); } catch (Exception e) { System.out.println("[MyCL classloader] unable to read class data:" + e); e.printStackTrace(); } return null; } @Nullable @Override public URL getResource(String name) { System.out.println("[MyCL classloader] trying getResource() on:" + name); URL res = null; // Delegate to the parent classloader for bootstrap and extension classes if (isBootstrapClass(name) || isExtensionClass(name)) { System.out.println("[MyCL classloader] {" + name + "} getResource() should be fetched by boostrap or extension classloader"); return super.getResource(name); } res = findResource(name); if (res == null) { System.out.println("[MyCL classloader] unable to getResource from current classloader"); res = super.getResource(name); } return res; } @Override public Enumeration<URL> getResources(String name) { System.out.println("[MyCL classloader] trying getResources() on:" + name); List<URL> allRes = new LinkedList<>(); try { // Delegate to the parent classloader for bootstrap and extension classes if (isBootstrapClass(name) || isExtensionClass(name)) { System.out.println("[MyCL classloader] {" + name + "} getResources() should be fetched by boostrap or extension classloader"); return super.getResources(name); } // load resource from this classloader Enumeration<URL> thisRes = findResources(name); if (thisRes != null) { while (thisRes.hasMoreElements()) { allRes.add(thisRes.nextElement()); } } // then try finding resources from parent classloaders Enumeration<URL> parentRes = super.findResources(name); if (parentRes != null) { while (parentRes.hasMoreElements()) { allRes.add(parentRes.nextElement()); } } } catch (IOException e) { System.out.println("[MyCL classloader] unable to getResources() from any classloader"); e.printStackTrace(); } System.out.println("[MyCL classloader] successfully executed getResources()"); return new Enumeration<URL>() { final Iterator<URL> it = allRes.iterator(); @Override public boolean hasMoreElements() { return it.hasNext(); } @Override public URL nextElement() { return it.next(); } }; } @Nullable @Override public InputStream getResourceAsStream(String name) { System.out.println("[MyCL classloader] trying getResourceAsStream() on:" + name); // Delegate to the parent classloader for bootstrap and extension classes if (isBootstrapClass(name) || isExtensionClass(name)) { System.out.println("[MyCL classloader] {" + name + "} getResourceAsStream() should be fetched by boostrap or extension classloader"); return super.getResourceAsStream(name); } InputStream inputStream = getResourceAsStream(name); if (inputStream != null) { System.out.println("[MyCL classloader] successfull executed getResourceAsStream()"); return inputStream; } return null; } }
错误堆栈跟踪:
➜ java git:(main) ✗ javac -J-Djava.class.path=/Users/gouravkumar/Desktop/Keploy/KPOCs/Class_Isolation_Loading/setCustomCL/target/setCustomCL-1.0-SNAPSHOT.jar -Xbootclasspath/p:/Users/gouravkumar/Library/Java/JavaVirtualMachines/corretto-1.8.0_342/Contents/Home/lib/tools.jar -J-Djava.system.class.loader=org.example.ccl.MyCL [MyCL classloader] Constructor:sun.misc.Launcher$AppClassLoader@18b4aac2 [MyCL classloader] loading class: com.sun.tools.javac.Main [MyCL classloader] {com.sun.tools.javac.Main} should be loaded by boostrap or extension classloader [MyCL classloader] trying getResource() on:sun/text/resources/unorm.icu [MyCL classloader] {sun/text/resources/unorm.icu} getResource() should be fetched by boostrap or extension classloader [MyCL classloader] loading class: com.sun.tools.javac.Main [MyCL classloader] {com.sun.tools.javac.Main} should be loaded by boostrap or extension classloader Error: Could not find or load main class com.sun.tools.javac.Main
问题分析与解决方案
类加载路径覆盖问题
你用-J-Djava.class.path直接覆盖了系统默认classpath,导致javac依赖的com.sun.tools.javac.Main等类无法被父类加载器找到。即便添加了-Xbootclasspath/p,父类加载器(AppClassLoader)的classpath已被替换为你的JAR,不包含tools.jar。getResourceAsStream递归调用问题
自定义类加载器的getResourceAsStream方法中,非bootstrap/extension资源分支里直接调用getResourceAsStream(name),会触发无限递归,导致资源无法加载或栈溢出。双亲委派逻辑违规
当前loadClass方法跳过了父类加载器的委托,直接加载非bootstrap/extension类,违反双亲委派模型,容易引发类加载冲突。
修复步骤:
修正classpath设置:不要直接覆盖系统classpath,改为追加自定义JAR:
# Linux/macOS环境 javac -J-Djava.class.path=$CLASSPATH:/Users/gouravkumar/Desktop/Keploy/KPOCs/Class_Isolation_Loading/setCustomCL/target/setCustomCL-1.0-SNAPSHOT.jar -J-Djava.system.class.loader=org.example.ccl.MyCL这样父类加载器既能找到
tools.jar类,也能加载自定义类加载器。修复递归调用问题:将
getResourceAsStream中的递归调用改为调用findResourceAsStream或父类方法:@Nullable @Override public InputStream getResourceAsStream(String name) { System.out.println("[MyCL classloader] trying getResourceAsStream() on:" + name); if (isBootstrapClass(name) || isExtensionClass(name)) { System.out.println("[MyCL classloader] {" + name + "} getResourceAsStream() should be fetched by boostrap or extension classloader"); return super.getResourceAsStream(name); } // 调用当前类加载器的资源查找方法,而非递归调用自身 InputStream inputStream = findResourceAsStream(name); if (inputStream != null) { System.out.println("[MyCL classloader] successfull executed getResourceAsStream()"); return inputStream; } // 当前加载器找不到时,委托父类加载 return super.getResourceAsStream(name); }修正双亲委派逻辑:遵循先父类后自身的加载顺序,仅在父类加载失败时才自行加载:
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { System.out.println("[MyCL classloader] loading class: " + name); Class<?> clazz = findLoadedClass(name); if (clazz != null) { System.out.println("[MyCL classloader] class already loaded:" + clazz); return clazz; } try { // 先委托父类加载所有类 clazz = super.loadClass(name, resolve); System.out.println("[MyCL classloader] loaded by parent:" + clazz); return clazz; } catch (ClassNotFoundException e) { System.out.println("[MyCL classloader] parent failed to load, trying self"); } // 父类加载失败时,再尝试自行加载 byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(name); } clazz = defineClass(name, classData, 0, classData.length); if (resolve) { resolveClass(clazz); } System.out.println("[MyCL classloader] succesfully loaded:" + clazz); return clazz; }
内容的提问来源于stack exchange,提问作者Gourav Kumar

