【发布时间】:2021-02-26 10:58:43
【问题描述】:
从Java SE Specifications 和“Inside Java Virtual Machine”等许多地方,他们都表示,当常量池条目的解析需要加载类型时,虚拟机使用加载引用类型的相同类加载器加载引用的类型。
但是,我发现这条规则并不总是成立:
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 和 specialCl 以加载 User 和 Main(我知道这种设置破坏了类加载器委托模型)。
运行这个例子会产生:
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方法被specialCl加载的对象u调用不是baseCL。这种现象与一开始所说的概念相反,真是奇怪,我不知道哪里出了问题。
此外,使用baseCl 加载类User 总是失败并显示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"。
我的问题是:
-
login的调用不应该立即引发错误吗? - 为什么以下使用
baseCL加载User失败并显示LinkageError?
任何帮助将不胜感激!
感谢 Frank Kieviet 的 blog,因为这个例子是根据他对 LinkageError 的研究构建的。
【问题讨论】:
-
你有什么问题?
-
为什么
login方法是用specialCl而不是baseCL加载的对象u调用的,我认为这个调用应该引发错误 -
文档声明“要解析未解析的符号引用...” -
Main由specialCL加载;它使用User,所以User也由specialCL加载(如文档所述);不需要baseCL加载User(只是我的猜测){关于“这种现象与一开始就陈述的概念相反,这真的很奇怪,我不知道哪里出了问题”}
标签: java classloader linkageerror