The question is published on by Tutorial Guruji team.
I have doubt with Class‘s identify,Class‘s identify should be The same classloader instance
+ The same class full path
。But I make some test cases,it doesn’t work。
I have a self-defined ClassLoader:
import java.io.*; public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } }
The Sample
class:
public class Sample { private Sample instance; public void setSample(Object instance) { this.instance = (Sample) instance; } }
And a test case:
String classDataRootPath = "/Users/haolin/Github/jvm/target/classes"; FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); String className = "me.hao0.jvm.classloader.Sample"; try { Class<?> class1 = fscl1.loadClass(className); Object obj1 = class1.newInstance(); Class<?> class2 = fscl2.loadClass(className); Object obj2 = class2.newInstance(); Method setSampleMethod = class1.getMethod("setSample", Object.class); setSampleMethod.invoke(obj1, obj2); } catch (Exception e) { e.printStackTrace(); }
It should occur ClassCastException
when setSampleMethod.invoke(obj1, obj2)
,because obj1 and obj2 are different Class (their ClassLoader is different),but the code works well, doesn’t throw ClassCastException
。
Could someone suggest it?
Answer
You would be correct except for one important detail. Classloaders exist in a hierarchy, and every ClassLoader
delegates to its parent before attempting to load a class itself. So if a common ancestor of your classloaders finds the requested class, then your classloaders will both return the same instance of that class. Consider this example:
public class Foo { public void isFoo(Object obj) { System.out.println("object is a Foo: " + (obj instanceof Foo)); Foo foo = (Foo) obj; } }
And the test:
import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class Test { public static void main(String[] args) throws Exception { File path = new File("."); URL[] urls = new URL[] { path.toURI().toURL() }; URLClassLoader cl1 = new URLClassLoader(urls); URLClassLoader cl2 = new URLClassLoader(urls); Class c1 = cl1.loadClass("Foo"); Class c2 = cl2.loadClass("Foo"); System.out.println("same class instance: " + (c1 == c2)); Object o1 = c1.newInstance(); Object o2 = c2.newInstance(); Method m = c1.getDeclaredMethod("isFoo", Object.class); m.invoke(o1, o2); } }
The output is:
same class instance: true object is a Foo: true
This occurs because the current directory is part of the default classpath, so the parent classloader finds and loads Foo
, and both custom classloaders return the instance from their parent.
Now, make this change to the test class and recompile:
File path = new File("foo");
Create the foo/
directory and move Foo.class
there. Now the output is:
same class instance: false object is a Foo: false Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at Test.main(Test.java:21) Caused by: java.lang.ClassCastException: Foo cannot be cast to Foo at Foo.isFoo(Foo.java:4) ... 5 more
This is what you expected. The system classloader can no longer find Foo
, so the custom classloaders load separate instances of it. The JVM sees the two Class
instances as different classes even though they’re identical, and the cast fails.