This code can load the same class twice by two distinct Classloader.
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
URL childAndParentClassesLocation = new URL("file:///C:\\Users\\Machi\\Desktop\\tobeloaded-1.0-SNAPSHOT.jar");
ClassLoader cl1 = new URLClassLoader(new URL[]{childAndParentClassesLocation}, Main.class.getClassLoader());
ClassLoader cl2 = new URLClassLoader(new URL[]{childAndParentClassesLocation}, Main.class.getClassLoader());
Class child1 = cl1.loadClass("example.ToBeLoaded");
Class child2 = cl2.loadClass("example.ToBeLoaded");
System.out.println(child2 == child1);
System.out.println(child2.equals(child1));
}
}
But a URLClassLoader has the Application ClassLoader as a parent. According to the parent delegation principle, should not the ToBeLoaded class be loaded by the Application ClassLoader once and only once?
If the parent delegation principle is in effect you are right. But that is not always the desired behaviour.
In JEE containers this is per design different. Just imagine two different applications refer to the same class (named mypackage.MyClass). But for some reason one application expects the library 'mypackage' in version 1, while the other in version two. And both of them are incompatible.
Thus JEE containers assign one classloader for each of the applications, and each of them will load that specific version of the class. They reside in memory in parallel, and they are distinct different types, so if you try to cast one into the other you will see an exception raised.
So to really know whether two classes are the same, not only compare their fully qualified class names but also compare the classloader they belong to. I am not aware that at runtime the JVM would check if two classes are the same (content-wise), as it might pop up in your example.
Related
I have a main method that creates custom classloader and instantiates a class, called Test, with it.
public class App {
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mycompany.app2.Test2"); // We ensure that Test2 is not part of current classpath
System.err.println("Should have thrown ClassNotFound");
System.exit(1);
} catch (ClassNotFoundException e) {
// ignore
}
String jar = "C:\\experiments\\classloader-test2\\target\\classloader-test2-1.0-SNAPSHOT.jar"; // Contains Test2
URL[] classPaths = new URL[] { new File(jar).toURI().toURL() };
ClassLoader classLoader = new URLClassLoader(classPaths, App.class.getClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
Class.forName("com.mycompany.app2.Test2", true, classLoader); // Check that custom class loader can find the wanted class
Test test = (Test) Class.forName("com.mycompany.app.Test", true, classLoader).getDeclaredConstructor().newInstance();
test.ex(); // This throws ClassNotFound for Test2
}
}
This class then itself instantiates another class that is not part of the original classpath, but is part of the custom one.
public class Test {
public void ex() {
new Test2().test();
}
}
In my understanding of classloader, since Test was created with the custom classloader any class loadings within should be done with the same loader. But this does not seem to be the case.
Exception in thread "main" java.lang.NoClassDefFoundError: com/mycompany/app2/Test2
at com.mycompany.app.Test.ex(Test.java:7)
at com.mycompany.app.App.main(App.java:28)
What do I need to do in the main method to make Test#ex work, without changing Test?
I'm using Java 17.
You create the URLClassLoader using App.class.getClassLoader() as the parent class loader. Hence, the request to load Test through the custom class loader is resolved through the parent loader, ending up at exactly the same class you’d get with Test.class in your main method.
You could pass a different parent loader, e.g. null to denote the bootstrap loader, to forbid resolving the Test class though the parent loader but this would result in either of two unhelpful scenarios
If the custom class loader has no com.mycompany.app.Test class on its own, the loading attempt would simply fail.
If the custom class loader has a com.mycompany.app.Test class, i.e. inside classloader-test2-1.0-SNAPSHOT.jar, it would be a different class than the Test class referenced in your main method, loaded by the application class loader. In this case, the type cast (Test) would fail.
In other words, the Test class referenced by you main method can not be affected by another, unrelated class loader at all.
There is an entirely different approach which may work in some scenarios. Do not create a new class loader, when all you want to do, is to inject a new class.
byte[] code;
try(var is = new URL("jar:file:C:\\experiments\\classloader-test2\\target\\" +
"classloader-test2-1.0-SNAPSHOT.jar!/com/mycompany/app2/Test2.class").openStream())
{
code = is.readAllBytes();
}
MethodHandles.lookup().defineClass(code);
Test test = new Test();
test.ex();
This adds the class to the current class loading context, so subsequent linking will succeed. With the following catches:
No previous attempt to link this class must have been made so far
It only works for a classes without dependencies to other absent classes (as there’s no class loader resolving those to the jar file outside the class path).
In some cases, when the dependencies are non-circular or resolved lazily, you could add all the classes with multiple define calls, if you know which you need and in which order.
The class must be in the same package, otherwise, you’d have to move the lookup context to the right package, with the documented restrictions
An entirely different approach to add the classes to the existing environment, would be via Java Agents, as they can add jar files to the class path.
What I understand is that if I use:
Instrumentation#getAllLoadedClasses()
I do get a selection of all loaded classes by the target JVM. But If I do:
Class.forName("my.class.name")
This will not be the same class as the class loaded by VM. Yes, I can add this particular class as a jar in the agent MANIFEST.MF Class-Path - but that does not look the same to me as getAllLoadedClasses().
Could someone please confirm whether this is correct i.e. I would not be able to find a specific class using Class.forName() when instrumenting? My objective was not to iterate over all loaded classes using getAllLoadedClasses() - But if there is no alternative, I guess that's okay for now.
** UPDATE
What I made a mistake in writing is the Boot-Class-Path which I have now corrected in my manifest. Using -verbose:class logging I managed to see that my jars are being loaded as
[Opened C:\fullpath\someother.jar]
[Opened C:\fullpath\another.jar]
[Opened C:\fullpath\different.jar]
But I don't see any corresponding loading information. I tried adding a Class.forName("a.package.in.someother.jar.classname") and got NoClassDefFoundError. As soon as I jump into the agent jar, I cannot use Class.forName() to check if the class is loaded by the target VM. I am getting a NoClassDefFoundError.
FURTHER UPDATE
Okay I have "Fattened" the manifest to look up all classes in my WEB-INF/lib and tomcat's lib directory. What I can see is below:
1) When my custom class MyClass is loaded for the first time. -verbose shows:
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
2) If I try to load the class again, it is correctly showing the above order.
3) My agent jar is manifested with all classes for my tomcat lib and my web-inf/lib directory. And I can also confirm that the loader sees the jars correctly.
4) Now I inject the agent, and call Class.forName("my.pkg.MyClass") from within the agent class. I get the below results.
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
I acknowledge that it's system class loader loding it inside my agent code as #RafaelWinterhalter pointed out in one of his answers. Is there any way I can force a "Delegation" so that the a different classloader loads the agent class and therefore, correctly redefines a class.
Any help is appreciated.
As it is stated in the javadoc:
Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of
the current class.
You can also see from the source code that the method is marked #CallerSensitive which means that you get a different result based on the class loader that invokes the method.
When calling Instrumentation::getAllLoadedClasses, the returned array contains classes of any class loader and not only of the current class loader which is the system class loader when running a Java agent. Therefore:
for (Class<?> type : instrumentation.getAllLoadedClasses()) {
assert type == Class.forName(type.getName());
}
is not generally true.
After a bit of run around, and Thanks to #Holger who reminded me what the problem was - incorrect class loader.
before I inject the agent, I have done the following:
// Get the current context class loader, which is app ext. classLoader
ClassLoader original = Thread.currentThread().getContextClassLoader().getSystemClassLoader();
// Set the system classloader to app classloader which won't delegate anything
Field scl = ClassLoader.class.getDeclaredFields();
scl.setAccessible(true);
scl.set(null, Thread.currentThread().getContextClassLoader());
// Now inject agent
try {
vm.loadAgent(agentPath, args);
} catch (all sorts of errors/exceptions in chain) {
// Log them and throw them back up the stack.
} finally {
vm.detach();
// Put back the classLoader linkage
sc.set(null, original);
}
How I have confirmed
When it goes in my Agent Class - Thread.currentThread().getContextClassLoader() becomes my application extn loader. But the system classloader now becomes `ParallelWebappClassLoader".
I am assuming this is how it works, but could be totally worng:
i) When I say Class.forName("my.pkg") it will check the system class loader which is pointing to my loader now. If the class is not found (i.e. not loaded), it will go to parents etc. I believe this is more or less the delegation model is.
ii) In this way, the class is loaded in VM by the same class loader which would also load the class in my webapp under normal circumstances.
iii) I will not instrument anything else apart from my own classes so the classloader will always be the same.
So far I have not seen any LinkageError happening. But I still feel this is too risky and if I break the link I am screwed.
Using Class.forName in a java profiler must be avoided to escape from the NoClassDef error. JVM Loads the class files in the different level of class loaders based on their classpath setting and the class file demand.
Java Cre libraries + Boot path defended libraries will be loaded in bootstrap Level
Java Agent will be loaded in system level and it goes on. Class.forName() will look the class files from the parent loaders, the current loader will not check the child loader (Until unless we implement our own loaders)
Java core classes will be accessible from your application code but our application code will not be accessible by the Java core classes. Its called class loader hierarchy.
You have three options.
Lookup the class files from the Instrumentation.GetLoadedClassFiles()
Through Transformers you can get all the loaders classes and you can track them and look for your class in every loader until you find.
Have the Class.forname implementation in the lowest level of the hierarchy so that it can internally access all the path.
Maintain the hierarchy properly to avoid too many weird errors.
Assuming you are looking for the Class<?> in order to re-transform a class, it seems to me that you could save the ClassLoader passed to your transformer, and later use ClassLoader.loadClass(String). Something like:
class MyTransformer implements ClassFileTransformer {
Map<String, ClassLoader> _name2loader = new ...;
...
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain pd,
byte[] classfileBuffer) throws ... {
...
_name2loader.put(className.replace("/","."), classLoader);
...
}
...
Class<?> getClass(String name) throws ClassNotFoundException {
ClassLoader cl = _name2loader.get(name);
if (cl == null) {
throw ClassNotFoundException("No loader for class " + name);
}
return cl.loadClass(name);
}
}
Note that the className passed to transform uses slashes, not dots... A better alternative than the String.replace may be to actually read the class name from the classfileBuffer using your bytecode library (such as javaassist or ASM, but if you're transforming bytecode, you're likely already using such a library).
Note: I'm not sure if you'd see the same class being passed for transformation with different ClassLoaders, but it would be good to look out for that (or research it).
i'm writing a custom class loader to load some of my classes (not all).
Class loader are very simple:
public Class loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class cls=findLoadedClass(className);
if(cls!=null) {
return cls;
}
// Search first for encrypted classes
cls=decryptClass(className);
if(cls==null) {
// Then try with default system classloader
cls=super.loadClass(className, resolve);
}
return cls;
}
And this is how i use it:
// In my Launcher class
public static void main(String[] args) {
MyClassLoader loader=new MyClassLoader();
try {
final Class main=loader.loadClass("com.MyAppMain");
Method toInvoke=main.getMethod("main", args.getClass());
toInvoke.invoke(main, new Object[]{args});
}
catch(Exception ex) {
}
}
All seem to be fine in my small test project, but when i use this loader in my big project(client-server application that use spring+hibernate and IoC) doesn't work.
I have not a particolar exception in my classloader, but for example, new Socket instance throw a "java.net.ConnectException: Connection refused" without a real reason...
Other problems is my main form does not become visible... and other strange problems like this.
So, the question, are these problems caused by my classloader that load in different way a different kind of classes?
Edit 1
My project use spring, so i use #Autowired or sometimes
springApplicationContext.getBean(clazz);
to inject a bean.
The problem is spring cannot find my beans if these classes are encrypted(so they need to be loaded by my classloader).
There is a workaround for this mistake?
Thanks.
Edit 2
I have set my classloader in spring ClassPathXmlApplicationContext and now i notice that spring uses my classloader to load beans class, but despite this it throws an org.springframework.beans.factory.NoSuchBeanDefinitionException becouse it cannot find beans... what can i do?
Thanks
I'm not very good at class loaders, but from you code it's only possible to assume, that in case your class loader can't find a class, it will redirect to system class loader. It may work fine when you run application standalone, like in your sample, but if it's a web application that is run on application server it will fail.
Application servers usually create a big hierarchy of class loaders and has a separate class loader used to load your application classes. In this case, system class loader knows nothing about your Spring related classes and thus can't load them.
You must keep in mind, that same class may be loaded by several class loaders and if you try to compare same class from different class loaders it will fail.
In your case I would set parent class loader in MyClassLoader constructor. As a parent class loader you can use MyClassLoader.class.getClassLoader() I think.
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
super(MyClassLoader.class.getClassLoader());
}
// other code
}
Hope this may help :)
I see two things that may be worth investigating:
The code makes the class loader parent-last. There is always a risk that this causes subtle class loading issues.
Maybe it is just the thread context class loader that is not set properly.
Here is the problem I'm running into. There's a huge legacy application that runs on java 1.3 and uses external API, say, MyAPI v1.0. The exact implementation of MyAPI 1.0 is located somewhere in the classpath used by the app. There's also a mechanism that allows that app to use external code (some kind of plugin mechanism). Now I have another java library (MyLib.jar) that uses MyAPI v2.0 (which is NOT 100% backward compatible with v1.0) and I have to use it from the original app using that plugin mechanism. So I have to somehow let two (incompatible!) versions of the same API work together. Specifically, I want to use MyAPI v2.0 when API classes are invoked from MyLib.jar classes and use MyAPI 1.0 in all other cases.
MyAPI 1.0 is in the classpath, so it will be used by default, that's ok. I can create my own version of class loader to load classes from MyAPI 2.0 - no problem. But how do I fit it all together? Questions:
MyLib.jar objects instantiate a lot(!) of instances of classes from MyAPI 2.0. Does this mean that I will have to do ALL these instantiations via reflection (specifying my own classloader)? That's a hell of a work!
If some of MyAPI 2.0 objects gets instantiated and it internally instantiates another object from MyAPI, what classloader will it use? Would it use my class loader or default one?
Just generally, does my approach sound reasonable? Is there a better way?
Let's start from answering your 2nd question: when referring from some class to another class it will be loaded by the same classloader that loaded the original class (unless of course the classloader didn't succeed in finding the class and then it will delegate to its parent classloader).
Having said that, why won't your entire MyLib.jar be loaded by a custom classloader, and then it can refer to the newer version of the API in a regular way. Otherwise you have a problem, because you will have to work with the Object type and reflection all the way through.
You need to be careful with class loaders. If you do what you were suggesting, you would almost always end up MyAPI 1.0 even when using your class loader for MyAPI 2.0. The reason for this is how classes are loaded using the class loader. Classes are always loaded from the parent class loader first.
"The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance. " (http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html)
To provide isolation between the two APIs properly, you would need 2 class loaders (or 2 in addition to the main application one).
Parent - System classloader
|- Classloader1 - Used to load MyAPI 1.0
|- Classloader2 - Used to load MyAPI 2.0
Now to your questions. What you probably want to do is move most of the logic that uses the API into the classloaders. In addition to MyAPI 1.0/2.0, you should load the part of the application that uses those. Then the parent application just has to call a method that uses the API. This way you make a single reflection call to start the application and everything inside that application just uses standard references.
You can do this with a fancy ClassLoader without reflection.
Basically, the classloader has to say "if the loading class is from this jar, load from classpath B, otherwise use the main ClassLoader".
It's a little more complicated than that, but if you start from that idea you'll get it worked out.
This sounds reasonable. In the [API javadoc for loadClass][1] it says:
"Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order:
Invoke findLoadedClass(String) to check if the class has already been loaded.
Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
Invoke the findClass(String) method to find the class."
If CL1 is for MyAPI1, CL2 is for MyAPI2, and CL3 is for MyLib it sounds like you want them to be checked in the order: CL3, CL2, CL1. Which from the above quote (as parents are checked first) suggests you want CL1 to have parent CL2, and CL2 to have parent CL3. As the constructor with a parent classloader is protected, you'll have to use URL classloader with the parents set appropriately.
Something like
URLCLassLoader cl3 = new URLClassLoader(new URL[]{ path to MyLib});
URLCLassLoader cl2 = new URLClassLoader(new URL[]{ path to API2}, cl3);
URLCLassLoader cl1 = new URLClassLoader(new URL[]{ path to API1}, cl2);
then use cl1 everywhere.
[1]: http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ClassLoader.html#loadClass(java.lang.String, boolean)
I have seen several places that "Class.getClassLoader() returns the ClassLoader used to load that particular class", and therefore, I am stumped by the results of the following example:
package test;
import java.lang.*;
public class ClassLoaders {
public static void main(String[] args) throws java.lang.ClassNotFoundException{
MyClassLoader mcl = new MyClassLoader();
Class clazz = mcl.loadClass("test.FooBar");
System.out.println(clazz.getClassLoader() == mcl); // prints false
System.out.println(clazz.getClassLoader()); // prints e.g. sun.misc.Launcher$AppClassLoader#553f5d07
}
}
class FooBar { }
class MyClassLoader extends ClassLoader { }
Shouldn't the statement clazz.getClassLoader() == mcl return true? Can someone explain what I am missing here?
Thanks.
Whenever you create your own classloader it will be attached in a tree-like hierarchy of classloaders. To load a class a classloader first delegates the loading to its parent. Only once all the parents didn't find the class the loader that was first asked to load a class will try to load it.
In your specific case the loading is delegated to the parent classloader. Although you ask you MyClassLoader to load it, it is the parent that does the loading. In this case it is the AppClassLoader.
Citing the API doc of ClassLoader:
Each instance of ClassLoader has an
associated parent class loader. When
requested to find a class or resource,
a ClassLoader instance will delegate
the search for the class or resource
to its parent class loader before
attempting to find the class or
resource itself.
If the selfdefined classloader delegates the call to classloader of the VM, who loads the class. clazz.getClassLoader() will return this classloader.
To get into the detail: The Javadoc of the class ClassLoader provides the following explanation of the order of executed steps:
Loads the class with the specified
binary name. The default
implementation of this method searches
for classes in the following order:
Invoke findLoadedClass(String) to check if the class has already been
loaded.
Invoke the loadClass method on the parent class loader. If the parent is
null the class loader built-in to the
virtual machine is used, instead.
Invoke the findClass(String) method to find the class.
As you inherited without a change the methods, this behaviour will be unchanged. Step 2 will be the one, where the class will be loaded. As you call the parameterless constructor of ClassLoader (automatically, as you didn't defined a constructor in MyClassLoader) you automatically make use of the builtin ClassLoader.