Reputation: 82
From many places like Java SE Specifications and "Inside Java Virtual Machine", they all state the notion that when the resolution of a constant pool entry requires loading a type, the virtual machine uses the same class loader that loaded the referencing type to load the referenced type.
However, I find out this rule doesn't always hold:
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);
}
}
I define a customized ClassLoader
which takes in its constructor a list of class names that should be defined by itself. Based on that, baseCL
is configured to load class Login
and User
and specialCl
to load User
and Main
(I know this setup breaks the class-loader delegation model).
Running this example produces:
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 ---
which says login
method is called with object u
loaded by specialCl
not baseCL
. This phenomenon is contrary to the notion stated from the beginning, which is really weird and I cannot figure out where goes wrong.
Further, usage of baseCl
to load class User
always fails with 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"
.
My question is:
login
immediately raise an error?baseCL
to load User
fails with LinkageError
?Any help would be appreciated!
Credit given to Frank Kieviet's blog as the example is constructed from his delving of LinkageError
.
Upvotes: 3
Views: 171
Reputation: 21560
Question 1: "Shouldn't the invocation of login
immediately raise an error?"
No, because the resolution according to Java SE Specification - Resolution is done only when any of the instructions anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield, and putstatic is executed.
Main.process()
) the resolution is done and succeedsLogin.login()
) no resolution is done because you don't invoke any of the User
class methods or access any of the fields of the class.u.getClass()
doesn't count because that resolves to the Object.getClass()
methodQuestion 2: "Why the following usage of baseCL
to load User
fails with LinkageError?
"
When the JVM loaded the Login
class it already had a class named User
available as loaded by the specialCL
. It stored the pair (specialCL, "User") in the loader constraints for the Login
class.
When the JVM now needs to resolve the User
class within the Login
class, it calls the Login
classes classloader to load the User
class. This loading however returns another User
class instance (baseCL, "User") then the one that was recorded in the loader contraints (specialCL, "User").
For a description of the loading constraints see Java SE Specification - Loading Constraints
Upvotes: 2