专栏首页算法之名类加载器的双亲委托模式

类加载器的双亲委托模式

遵照之前解析反射 中,我们说到类的加载器ClassLoader在对类进行加载的时候,默认会使用双亲委托模式。系统会判断当前类是否已经被加载,如果已经被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载。

我们现在来对此加以验证,先写一个简单类HelloLoader

public class HelloLoader {
    public void print() {
        System.out.println("I am in Boot ClassLoader");
    }
}

再写一个调用类FindClassOrder

public class FindClassOrder {
    public static void main(String[] args) {
        HelloLoader loader = new HelloLoader();
        loader.print();
    }
}

这两个类放在同一个包下面,我这里的包名为com.guanjian.calculate.test

运行结果

I am in Boot ClassLoader

现在将生成的HelloLoader.class文件拷贝出来,放到我事先准备的文件夹下面,目录为/Users/admin/Downloads/com/guanjian/calculate/test,本人的系统为mac的,注意该文件夹不与我们的工程文件夹相同,我们之前到工程文件夹为/Users/admin/Downloads/calculate

此时修改HelloLoader的代码

public class HelloLoader {
    public void print() {
        System.out.println("I am in App ClassLoader");
    }
}

此时运行FindClassOrder,结果如下

I am in App ClassLoader

现在给FindClassOrder添加JVM参数-Xbootclasspath/a:/Users/admin/Downloads/

再次运行,结果发生了改变

I am in Boot ClassLoader

给FindClassOrder添加一条代码,如下

public class FindClassOrder {
    public static void main(String[] args) {
        HelloLoader loader = new HelloLoader();
        loader.print();
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
}

运行结果

I am in Boot ClassLoader /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/classes:/Users/admin/Downloads/

从最后一部分/Users/admin/Downloads/我们可以看到,该目录已经加入到了启动的ClassPath中。

由此我们可以看出,HelloLoader的应用版本并没有运行,取而代之的是启动版本。当系统需要加载一个类时,会先从顶层的启动类加载器开始加载,逐层向下,直到找到该类。

现在我们将FindClassOrder修改如下

public class FindClassOrder1 {
    public static void main(String[] args) throws Exception {
        //获取FindClassOrder1的类加载器,此处为一个应用类加载器
        ClassLoader cl = FindClassOrder1.class.getClassLoader();
        //将HelloLoader.class字节码转换成字节数组
        byte[] bHelloLoader = loadClassBytes("com.guanjian.calculate.test.HelloLoader");
        //ClassLoader的defineClass()方法可以将字节数组转换成Class
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        //获取Class HelloLoader
        md_defineClass.invoke(cl, bHelloLoader, 0, bHelloLoader.length);
        md_defineClass.setAccessible(false);

        HelloLoader loader = new HelloLoader();
        //打印loader对象的类加载器
        System.out.println(loader.getClass().getClassLoader());
        loader.print();
    }

    private static byte[] loadClassBytes(String className) throws ClassNotFoundException {
        try {
            //获取class文件的完整路径
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new
                    ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException fnfe) {
            throw new ClassNotFoundException(className);
        }
    }

    private static String getClassFile(String name) {
        StringBuilder sb = new StringBuilder("/Users/admin/Downloads/calculate/target/classes");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + name);
        return sb.toString();
    }
}

添加JVM参数-Xbootclasspath/a:/Users/admin/Downloads/

运行结果如下

sun.misc.Launcher$AppClassLoader@18b4aac2 I am in App ClassLoader

由此我们可以看出,虽然定义了启动ClassPath,但是系统并没有加载启动版本的HelloLoader。当判断类是否需要加载时,是从底层的应用类加载器开始判断的,如果已经在应用类加载器加载过了,就不会请求上层类加载器了。

我们再将FindClassOrder做出修改

public class FindClassOrder2 {
    public static void main(String[] args) throws Exception {
        //获取FindClassOrder1的类加载器,此处为一个应用类加载器
        ClassLoader cl = FindClassOrder1.class.getClassLoader();
        //将HelloLoader.class字节码转换成字节数组
        byte[] bHelloLoader = loadClassBytes("com.guanjian.calculate.test.HelloLoader");
        //ClassLoader的defineClass()方法可以将字节数组转换成Class
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        //获取Class HelloLoader
        md_defineClass.invoke(cl, bHelloLoader, 0, bHelloLoader.length);
        md_defineClass.setAccessible(false);
        //通过应用类加载器的双亲扩展类加载器进行加载
        //如果扩展类加载器加载不了,则会请求扩展类加载器的双亲启动类加载器进行加载
        Object loader = cl.getParent().loadClass("com.guanjian.calculate.test.HelloLoader").newInstance();
        //打印loader对象的类加载器
        System.out.println(loader.getClass().getClassLoader());
        //获取loader对象的print()方法
        Method m = loader.getClass().getMethod("print", null);
        //执行print()方法
        m.invoke(loader,null);
    }

    private static byte[] loadClassBytes(String className) throws ClassNotFoundException {
        try {
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new
                    ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException fnfe) {
            throw new ClassNotFoundException(className);
        }
    }

    private static String getClassFile(String name) {
        StringBuilder sb = new StringBuilder("/Users/admin/Downloads/calculate/target/classes");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + name);
        return sb.toString();
    }
}

添加JVM参数-Xbootclasspath/a:/Users/admin/Downloads/

运行结果

null I am in Boot ClassLoader

通过结果,我们可以看出,这里是调用了启动类加载器进行加载的,因为启动类加载器在Java中没有对象,是用C写的,所以为null;而打印的I am in Boot ClassLoader也正好说明加载的是启动ClassPath下的那个HelloLoader.class。虽然在扩展类加载器加载HelloLoader之前,该类已经在应用类加载器中了,但是扩展类加载器并不会向应用类加载器进行确认,而是只在自己的路径中查找,并最终委托给了启动类加载器,而非应用类加载器,从这里可以看到,在判断类是否已经加载时,顶层类加载器不会询问底层类加载器。判断类是否加载时,应用类加载器会顺着双亲路径往上判断,直到启动类加载器。但是启动类加载器不会往下询问,这个委托路线是单向的。

双亲委托模式的弊端

之前说的,检查类是否已经加载的委托过程是单向的。这种方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中,提供了一个接口,该接口需要在应用类得以实现,该接口还绑定了一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

双亲委托模式的补充

在Java平台中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,通常可以称为Service Provider Interface.即SPI

我们来看一段这样的实现,它是实现javax.xml.parsers中XML文件解析功能的代码

在DocumentBuilderFactory中,这是一个抽象类,加载在启动类加载器中。

public static DocumentBuilderFactory newInstance() {
    //根据抽象父类来获取一个子类的对象,抽象父类是通过启动类加载器加载,而子类是通过应用类加载器加载
    return FactoryFinder.find(
            /* The default property name according to the JAXP spec */
            DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
            /* The fallback implementation class name */
            "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
}

在FactoryFinder中

private static final SecuritySupport ss = new SecuritySupport(); //在获取系统资源的时候用于检查访问控制的安全支持类
static <T> T find(Class<T> type, String fallbackClassName)
    throws FactoryConfigurationError
{
    //获取类的名称
    final String factoryId = type.getName();
    dPrint("find factoryId =" + factoryId);

    // Use the system property first
    try {
        //从系统属性中获取资源名称
        String systemProp = ss.getSystemProperty(factoryId);
        //如果该资源名称不为空,则返回type类的实例化对象
        if (systemProp != null) {
            dPrint("found system property, value=" + systemProp);
            return newInstance(type, systemProp, null, true);
        }
    }
    catch (SecurityException se) {
        if (debug) se.printStackTrace();
    }

    //尝试从$java.home/lib/jaxp.properties文件读取资源
    try {
        if (firstTime) {
            synchronized (cacheProps) {
                if (firstTime) {
                    String configFile = ss.getSystemProperty("java.home") + File.separator +
                        "lib" + File.separator + "jaxp.properties";
                    File f = new File(configFile);
                    firstTime = false;
                    if (ss.doesFileExist(f)) {
                        dPrint("Read properties file "+f);
                        cacheProps.load(ss.getFileInputStream(f));
                    }
                }
            }
        }
        final String factoryClassName = cacheProps.getProperty(factoryId);
        //如果可以读取,则返回实例化type类的对象
        if (factoryClassName != null) {
            dPrint("found in $java.home/jaxp.properties, value=" + factoryClassName);
            return newInstance(type, factoryClassName, null, true);
        }
    }
    catch (Exception ex) {
        if (debug) ex.printStackTrace();
    }

    //尝试用SPI方式
    T provider = findServiceProvider(type);
    if (provider != null) {
        return provider;
    }
    if (fallbackClassName == null) {
        throw new FactoryConfigurationError(
            "Provider for " + factoryId + " cannot be found");
    }

    dPrint("loaded from fallback value: " + fallbackClassName);
    return newInstance(type, fallbackClassName, null, true);
}
private static <T> T findServiceProvider(final Class<T> type) {
    try {
        return AccessController.doPrivileged(new PrivilegedAction<T>() {
            public T run() {
                //通过把type类实例,这里是DocumentBuilderFactory.class以及通过Thread.currentThread().getContextClassLoader()获取的应用类加载器
                //放入serviceLoader对象的惰性迭代器lookupIterator属性中
                final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                
                final Iterator<T> iterator = serviceLoader.iterator();
                if (iterator.hasNext()) {
                    return iterator.next();
                } else {
                    return null;
                }
             }
        });
    } catch(ServiceConfigurationError e) {
        // It is not possible to wrap an error directly in
        // FactoryConfigurationError - so we need to wrap the
        // ServiceConfigurationError in a RuntimeException.
        // The alternative would be to modify the logic in
        // FactoryConfigurationError to allow setting a
        // Throwable as the cause, but that could cause
        // compatibility issues down the road.
        final RuntimeException x = new RuntimeException(
                "Provider for " + type + " cannot be created", e);
        final FactoryConfigurationError error =
                new FactoryConfigurationError(x, x.getMessage());
        throw error;
    }
}
public static <S> ServiceLoader<S> load(Class<S> service) {
    //获取一个当前线程的上下文加载器,该上下文加载器就是应用类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}
private final Class<S> service; //已经被启动类加载器加载过的类,这里指的是DocumentBuilderFactory.class
private final ClassLoader loader; //类加载器,用于判定获取的是哪一种类加载器,如启动类加载器或应用类加载器
private final AccessControlContext acc; //访问控制权限
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    //如果svc类已经被加载了,返回该类实例,否则抛出异常
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //传入的类加载器为应用类加载器,如果该应用类加载器不为空,则为应用类加载器,否则为启动类加载器
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}
private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); //按照实例化顺序的缓存
private LazyIterator lookupIterator; //当前的惰性查找迭代器,ServiceLoader的内部类
public void reload() {
    //清空加载缓存
    providers.clear();
    //将DocumentBuilderFactory.class以及应用类加载器传入惰性查找迭代器中生成对象
    lookupIterator = new LazyIterator(service, loader);
}

我们来看一下LazyIterator整个的定义

private static final String PREFIX = "META-INF/services/";
private class LazyIterator
    implements Iterator<S>
{

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    private boolean hasNextService() {
        //如果nextName不为空,表示有下一个类实例
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                //获取META-INF/services/加类全名组成的路径
                String fullName = PREFIX + service.getName();
                //如果应用类加载器为空,从加载类的搜索路径查找指定名称的资源
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                //如果应用类加载器不为空,启用双亲加载器查找资源
                else
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }

    /**
     * 从资源文件加载子类
     */
    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            //使用应用类加载器loader加载子类c
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        //如果c不是DocumentBuilderFactory.class的子类,抛出异常
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            //将c的对象实例强转成DocumentBuilderFactory类型的对象
            S p = service.cast(c.newInstance());
            //添加进缓存
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }

    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public S next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

}
public Iterator<S> iterator() {
    return new Iterator<S>() {
        //通过缓存获取已加载的迭代器集合
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
        /**
         * 是否有加载的下一个类实例
         */
        public boolean hasNext() {
            //如果缓存中有下一个加载的类实例,返回true
            if (knownProviders.hasNext())
                return true;
            //如果缓存中没有,返回惰性查找迭代器中查找结果
            return lookupIterator.hasNext();
        }

        public S next() {
            //如果缓存中有下一个加载的类实例,直接从缓存中获取
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            //如果缓存中没有,返回惰性查找迭代器的下一个类实例
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

通过以上分析,我们可以看到,DocumentBuilderFactory.class的子类并不是由启动类加载器进行加载的,当然启动类加载器也加载不了这个子类,而是使用的Thread.currentThread().getContextClassLoader()上下文应用类加载器来进行加载的,该加载器成为了一个相对共享的实例。这样,即使是在启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了。

突破双亲的限制

当我们使用自定义类加载器的时候,当对类进行加载的时候,虽然它自己加载不了,会委托双亲应用类加载器进行加载,但是当我们来看这个类实际的加载器的时候,我们会看到是自定义加载器加载的。(有点玄幻)

首先我们先写一个OrderClassLoader继承于ClassLoader,重写父类的loadClass()和findClass()方法

@AllArgsConstructor
public class OrderClassLoader extends ClassLoader {
    private String fileName;

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> re = findClass(name);
        if (re == null) {
            System.out.println("I can't load the class:" + name + " need help from parent");
            return super.loadClass(name, resolve);
        }
        return re;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        Class<?> clazz = this.findLoadedClass(className);
        if (clazz == null) {
            try {
                byte[] bytes = loadClassBytes(className);
                clazz = defineClass(className,bytes,0,bytes.length);
            } catch (Exception e) {
//                e.printStackTrace();
                return null;
            }
        }
        return clazz;
    }

    private byte[] loadClassBytes(String className) throws ClassNotFoundException {
        try {
            //获取class文件的完整路径
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new
                    ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException fnfe) {
//            throw new ClassNotFoundException(className);
            return null;
        }
    }

    private String getClassFile(String name) {
        StringBuilder sb = new StringBuilder(fileName);
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + name);
        return sb.toString();
    }
}

调用这个自定义加载器

public class OrderClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        OrderClassLoader myLoader = new OrderClassLoader("/Users/admin/Downloads/");
        Class<?> clz = myLoader.loadClass("com.guanjian.calculate.test.HelloLoader");
        System.out.println(clz.getClassLoader());

        System.out.println("==== Class Loader Tree ====");
        ClassLoader cl = clz.getClassLoader();
        while (cl != null) {
            System.out.println(cl);
            cl = cl.getParent();
        }
    }
}

运行结果

I can't load the class:java.lang.Object need help from parent com.guanjian.calculate.test.OrderClassLoader@85ede7b ==== Class Loader Tree ==== com.guanjian.calculate.test.OrderClassLoader@85ede7b sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@63961c42

从结果可以看出,光靠自定义加载器没办法把字节数组转换成Class实例,而且自定义加载器的defineClass()方法一直找到了HelloLoader的祖先类Object.class。最后使用super.loadClass(name, resolve)应用类加载器来加载成功。但我们打印clz.getClassLoader()得到的却是自定义加载器OrderClassLoader@85ede7b。

热替换

热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。对于Java来说,热替换并不是天生就支持的特性,如果一个类已经加载到了系统中,通过修改类文件,并无法让系统再来加载并重新定义这个类。因此,在Java中实现这一功能的一个可行方法就是灵活运用ClassLoader。

我们还是用前面写的OrderClassLoader自定义加载器来说明。我们新写一个调用类

public class OrderClassLoaderTest1 {
    public static void main(String[] args) {
        while (true) {
            try {
                OrderClassLoader myLoader = new OrderClassLoader("/Users/admin/Downloads/");
                Class<?> clz = myLoader.loadClass("com.guanjian.calculate.test.HelloLoader");
                Object demo = clz.newInstance();
                Method print = demo.getClass().getMethod("print", new Class[]{});
                print.invoke(demo,new Object[]{});
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行以后会处于无限循环,每三秒打印一次

I can't load the class:java.lang.Object need help from parent I can't load the class:java.lang.System need help from parent I can't load the class:java.io.PrintStream need help from parent I am in Boot ClassLoader

现在我们将工程中targets/classes里面的文件替换掉/Users/admin/Downloads/com/guanjian/calculate/test中的HelloLoader.class。现在我们在没有停止程序运行的情况下打印结果如下,每三秒打印一次

I can't load the class:java.lang.Object need help from parent I can't load the class:java.lang.System need help from parent I can't load the class:java.io.PrintStream need help from parent I am in App ClassLoader

这里有一点需要注意,当我们实例化对象的时候不能写成如下的方式

public class OrderClassLoaderTest1 {
    public static void main(String[] args) {
        while (true) {
            try {
                OrderClassLoader myLoader = new OrderClassLoader("/Users/admin/Downloads/");
                Class<?> clz = myLoader.loadClass("com.guanjian.calculate.test.HelloLoader");
                HelloLoader demo = (HelloLoader)clz.newInstance();
                Method print = demo.getClass().getMethod("print", new Class[]{});
                print.invoke(demo,new Object[]{});
                Thread.sleep(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

这么写的话,会抛出如下的异常

java.lang.ClassCastException: com.guanjian.calculate.test.HelloLoader cannot be cast to com.guanjian.calculate.test.HelloLoader at com.guanjian.calculate.test.OrderClassLoaderTest1.main(OrderClassLoaderTest1.java:11)

这是因为myLoader每一次都是一个新的自定义加载器对象,所以每次都是不同的加载器,不同的加载器加载同一个类会被虚拟机认为是不同的类。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 图的邻接矩阵数据结构(基础) 顶

    算法之名
  • 分布式事务 顶

    上图中JTA是事务管理器在Java中的实现,它的全称为Java Transaction API.XAResource是Java中对Resource规范的实现。

    算法之名
  • k8s集群部署四(部署Flannel网络) 顶

    wget https://github.com/coreos/flannel/releases/download/v0.9.1/flannel-v0.9.1-l...

    算法之名
  • 深入理解Java虚拟机(类加载机制)

    我们的源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行。虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始...

    张磊BARON
  • 一步之差进入大厂,下定决心钻透java所有面试题,顺利通过!

    这篇文章真的是给大家分享一篇我自己血的教训,本人是一个勤勤恳恳、任劳任怨的java程序猿一直都在自己的岗位上发光发热,耐不住今年疫情小心思就发芽了,想要跳槽到大...

    程序员白楠楠
  • Java类加载到类使用全过程

    上篇我们说到为了减少Activity类加载的过程,所以可以预创建Activity。

    码上积木
  • 深入JVM类加载机制

    ClassLoader顾名思义就是我们所常见的类加载器,其作用就是将编译后的class文件加载内存当中.在应用启动时,JVM通过ClassLoader加载相关的...

    用户7886150
  • JVM真香系列:轻松理解class文件到虚拟机(下)

    上面我们自定义一个String出了问题,问题在于JVM不知道我们想用哪个类,于是JVM就定义了个规范。

    田维常
  • 【JVM系统学习之路】一篇看懂类加载

    嗨喽,小伙伴大家好,我是小春哥,今天是打卡 【JVM系统学习之路】 的第二篇文章 类加载子系统 ,在学习本章节首先回顾 上一章节【JVM系统学习之路】JVM与J...

    山间木匠
  • Java类加载机制

    答:当某个类加载器在接到加载类的请求时,会将加载任务委托给父类加载器,依次递归,父类加载器可以完成类加载任务,就成功返回;不能加载则子类加载器自己完成加载。有3...

    宇宙之一粟

扫码关注云+社区

领取腾讯云代金券