You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义类加载器替换系统类加载器时找不到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

问题分析与解决方案

  1. 类加载路径覆盖问题
    你用-J-Djava.class.path直接覆盖了系统默认classpath,导致javac依赖的com.sun.tools.javac.Main等类无法被父类加载器找到。即便添加了-Xbootclasspath/p,父类加载器(AppClassLoader)的classpath已被替换为你的JAR,不包含tools.jar。

  2. getResourceAsStream递归调用问题
    自定义类加载器的getResourceAsStream方法中,非bootstrap/extension资源分支里直接调用getResourceAsStream(name),会触发无限递归,导致资源无法加载或栈溢出。

  3. 双亲委派逻辑违规
    当前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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 01:05:30