3

从Java SE 规范和“ Java 虚拟机内部”等许多地方,他们都指出,当常量池条目的解析需要加载类型时,虚拟机使用加载引用类型的相同类加载器来加载引用类型。

但是,我发现这条规则并不总是成立:

package com.alaneuler.test;

import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class LinkageMain {
    static class MyClassLoader extends ClassLoader {
        private Set<String> selfFirstClasses;
        private String name;

        public MyClassLoader(String name, ClassLoader parent, String... selfFirstNames) {
            super(parent);
            this.name = name;
            selfFirstClasses = new HashSet<>(Arrays.asList(selfFirstNames));
        }

        @Override
        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            if (selfFirstClasses.contains(name)) {
                String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                try (InputStream is = getClass().getResourceAsStream(filename)) {
                    byte[] buf = new byte[is.available()];
                    int len = is.read(buf);
                    System.out.println(this.name + ": loading " + name);
                    return defineClass(name, buf, 0, len);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (!name.startsWith("java.")) {
                System.out.println(this.name + ": super.loadClass(" + name + ")");
            }
            return super.loadClass(name, resolve);
        }

        @Override
        public String toString() {
            return name;
        }
    }

    public static class User {}
    public static class Login {
        public void login(User u) {
            System.out.println("--- login called with user loaded by " + u.getClass().getClassLoader()  + " ---");
        }
    }
    public static class Main {
        public static void process() {
            User u = new User();
            new Login().login(u);
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader baseCL = new MyClassLoader("Base", LinkageMain.class.getClassLoader(),
                "com.alaneuler.test.LinkageMain$User",
                "com.alaneuler.test.LinkageMain$Login");
        MyClassLoader specialCL = new MyClassLoader("specialCL", baseCL,
                "com.alaneuler.test.LinkageMain$User",
                "com.alaneuler.test.LinkageMain$Main");

        specialCL.loadClass("com.alaneuler.test.LinkageMain$Main").getMethod("process").invoke(null);
    }
}

我定义了一个自定义的ClassLoader,它在它的构造函数中接受一个应该由它自己定义的类名列表。基于此,baseCL配置为加载类和加载和Login(我User知道这个设置破坏了类加载器委托模型)。specialClUserMain

运行此示例会产生:

specialCL: loading com.alaneuler.test.LinkageMain$Main
specialCL: loading com.alaneuler.test.LinkageMain$User
specialCL: super.loadClass(com.alaneuler.test.LinkageMain$Login)
Base: loading com.alaneuler.test.LinkageMain$Login
--- login called with user loaded by specialCL ---

它说login方法是用notu加载的对象调用的。这种现象与一开始所说的概念相反,这真的很奇怪,我不知道哪里出了问题。specialCl baseCL

此外,使用baseClto load classUser总是失败并显示LinkageError: loader constraint violation: loader (instance of com/alaneuler/test/LinkageMain$MyClassLoader) previously initiated loading for a different type with name "com/alaneuler/test/LinkageMain$User"

我的问题是:

  1. 不应该调用login立即引发错误吗?
  2. 为什么以下baseCL加载的用法User失败了LinkageError

任何帮助,将不胜感激!

归功于 Frank Kieviet 的博客,因为示例是从他对LinkageError.

4

1 回答 1

2

问题 1:“不应该调用login立即引发错误吗?”

不,因为根据Java SE 规范的解析 - 解析仅在anewarraycheckcastgetfieldgetstaticinstanceofinvokedynamicinvokeinterfaceinvokespecialinvokestaticinvokevirtualldcldc_wmultianewarraynewputfield、并执行putstatic

  • 在调用方 (in Main.process()) 解析完成并成功
  • 在被调用方 (in Login.login()) 没有完成解析,因为您不调用任何User类方法或访问类的任何字段。
  • 方法调用u.getClass()不算数,因为它解析为Object.getClass()方法

问题 2:“为什么以下baseCL加载的用法User失败了LinkageError?

当 JVM 加载Login该类时,它已经有一个名为Useravailable 的类,由specialCL. 它将对 (specialCL, "User") 存储在Login类的加载器约束中。

当 JVM 现在需要解析User类中的Login类时,它会调用Loginclasses 类加载器来加载User类。但是,此加载返回另一个User类实例(baseCL,“User”),然后是记录在加载程序约束中的实例(specialCL,“User”)。

有关加载约束的描述,请参阅Java SE 规范 - 加载约束

于 2021-02-26T18:04:39.073 回答