How do you change the CLASSPATH of a Java process from within the Java process?
Before you ask me "Why would you want to do that?" I'll explain it shortly.
When you have a Clojure REPL running it is common to need more jars in your CLASSPATH to load a Clojure source file, and I'd like to do it without having to restart Clojure itself (which is not really an option when using it on Slime on Emacs).
That's the reason but I don't want this question tagged as some-weird-language some-weird-editor and be disregarded by the majority of Java developers that may have the answer.
Update Q4 2017: as commented below by vda8888, in Java 9, the System java.lang.ClassLoader is no longer a java.net.URLClassLoader.
See "Java 9 Migration Guide: The Seven Most Common Challenges"
The class loading strategy that I just described is implemented in a new type and in Java 9 the application class loader is of that type.
That means it is not a URLClassLoader anymore, so the occasional (URLClassLoader) getClass().getClassLoader() or (URLClassLoader) ClassLoader.getSystemClassLoader() sequences will no longer execute.
java.lang.ModuleLayer would be an alternative approach used in order to influence the modulepath (instead of the classpath). See for instance "Java 9 modules - JPMS basics".
For Java 8 or below:
Some general comments:
you cannot (in a portable way that's guaranteed to work, see below) change the system classpath. Instead, you need to define a new ClassLoader.
ClassLoaders work in a hierarchical manner... so any class that makes a static reference to class X needs to be loaded in the same ClassLoader as X, or in a child ClassLoader. You can NOT use any custom ClassLoader to make code loaded by the system ClassLoader link properly, if it wouldn't have done so before. So you need to arrange for your main application code to be run in the custom ClassLoader in addition to the extra code that you locate.
(That being said, cracked-all mentions in the comments this example of extending the URLClassLoader)
And you might consider not writing your own ClassLoader, but just use URLClassLoader instead. Create a URLClassLoader with a url that are not in the parent classloaders url's.
URL[] url={new URL("file://foo")};
URLClassLoader loader = new URLClassLoader(url);
A more complete solution would be:
ClassLoader currentThreadClassLoader
= Thread.currentThread().getContextClassLoader();
// Add the conf dir to the classpath
// Chain the current thread classloader
URLClassLoader urlClassLoader
= new URLClassLoader(new URL[]{new File("mtFile").toURL()},
currentThreadClassLoader);
// Replace the thread classloader - assumes
// you have permissions to do so
Thread.currentThread().setContextClassLoader(urlClassLoader);
If you assume the JVMs system classloader is a URLClassLoader (which may not be true for all JVMs), you can use reflection as well to actually modify the system classpath... (but that's a hack;)):
public void addURL(URL url) throws Exception {
URLClassLoader classLoader
= (URLClassLoader) ClassLoader.getSystemClassLoader();
Class clazz= URLClassLoader.class;
// Use reflection
Method method= clazz.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(classLoader, new Object[] { url });
}
addURL(new File("conf").toURL());
// This should work now!
Thread.currentThread().getContextClassLoader().getResourceAsStream("context.xml");
I don't believe you can - the right thing to do (I believe) is create a new classloader with the new path. Alternatively, you could write your own classloader which allows you to change the classpath (for that loader) dynamically.
There's no need to write your own class loader! There's clojure.lang.DynamicClassLoader.
http://blog.japila.pl/2011/01/dynamically-redefining-classpath-in-clojure-repl/
You may want to look into using java.net.URLClassLoader. It allows you to programmatically load classes that weren't originally in your classpath, though I'm not sure if that's exactly what you need.
It is possible as seen from the two links below, the method VonC gives seems to be the best but check out some of these posts and google for "Java Dynamic Classpath" or "Java Dynamic Class Loading" and find out some info from there.
I'd post in more depth but VonC has pretty much done the job.
From Dynamic loading of class and Jar files.
Also check this sun forum post.
String s="java -classpath abcd/ "+pgmname+" "+filename;
Process pro2 = Runtime.getRuntime().exec(s);
BufferedReader in = new BufferedReader(new InputStreamReader(pro2.getInputStream()));
is an example of changin the classpath in java program
Related
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).
Assuming the following loader definition (the example is written in scala but it is almost the same as Java, thus you should easily understand the problem):
jar = new File("path/to/myjar.jar")
url = jar.toURI.toURL
urls = Array[URL](url) // just a single-element array containing the url
loader = new URLClassLoader(urls)
The JAR myjar.jar contains org/pack/Simple.class. The Simple has standard package org.pack and was compiled and packed into the JAR via JavaCompiler. The JAR is correct (executable etc.)
On linux, loader.loadClass("org.pack.Simple") returns the correct Class object.
On windows, it throws a ClassNotFoundException. Of course, I use \ instead of / as a file separator.
What do I do wrong? Or does simply windows suck? I installed OracleJDK8 and added its bin/ folder to the path.
EDIT1:
If I unpack JAR into some directory and let the ClassLoader to read from URL pointing at that directory, everything works. How is this possible?
I struggled long with this problem yesterday, but after a long Googling session I stumbled on the solution.
The URL object you specify must be created as follows:
URLClassLoader.addURL(new File(pathParam).toURI().toURL());
or in the constructor as follows:
new URLClassLoader(new URL[]{new File(pathParam).toURI().toURL()},this.getClass().getClassLoader());
Note that the constructor has the parent class-loader as a parameter.
for the loadClass() method. You need to ensure the path to the class is in binary format (as you are doing already).
For some reason, even if I create a URL object using the URL(String) constructor it won't work.
If you go and look at the java.lang.ClassLoader super-class, you will notice that it throws an exception by default. This is why the classloader is important.
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
I have an application that allows, using an abstract class, people to write their own implementations. I load these implementations as .class-files from a directory. Currently, I have this solution:
File classDir = new File("/users/myproject/classes/");
URL[] url = { classDir.toURI().toURL() };
URLClassLoader urlLoader = new URLClassLoader(url);
String filename;
for (File file : classDir.listFiles()) {
filename = string.getFilenameWithoutExtension(file);
if (filename.equals(".") || filename.equals("..") || filename.startsWith("."))
continue;
AbstractClass instance = (AbstractClass)urlLoader
.loadClass("org.mypackage." + filename)
.getConstructor(ConfigUtil.class, DatabaseUtil.class, StringUtil.class)
.newInstance(config, database, string));
instance.doSomething();
}
As you see - I need to specify the package the classes are located in in order to correctly load them. Omitting the package, I get an
java.lang.NoClassDefFoundError:
MyClass (wrong name: org/mypackage/MyClass)
error.
Now, from a architectural POV, I think it is very ill-designed that classes other people designed have to be compiled to MY package when loading them.
So I ask you: Is there a way I can load classes form the file system without having to specify the package they reside in?
Yes; implement an interface (or use an annotation).
Then use any class-scanning library (there are lots of SO questions about this, like this one) to load the particular class in question. Searching for "Java class scanning" or "Java plugin mechanism" will help.
You might also just want to use the Java Plugin Framework and avoid some effort. Although it's not clear to me that it's maintained any more, I know people are still using it.
You can use the ServiceProvider to load implementations which you don't know.
If I have multiple files of the same name on classpath (e.g. I have multiple .jar with log4j.properties), what are the rules JVM follows to chose one?
It is specified by the order in which the resources (i.e. usually jar files) are specified using -classpath option. Resources 'earlier' on the classpath take precedence over resources that are specified after them. This can be also set in the manifest file of your application and then you don't need to provide -classpath option. You may want to check these articles on how to work with manifest files.
The exhaustive description of "how classes are found" can be found here, where the section on JAR-class-path Classes describes the logic of JAR-files searching.
The ClassLoader determines where a resource will be located (taken from ClassLoader JavaDoc):
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.
So wherever in your code Class#getResource or Class#getResourceAsStream is called, this happens (taken from Class.java)
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
ClassLoader.java:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
where ClassLoader#findResource is actually to be overwritten by the ClassLoader implementation. This implies that the behavior is different on an application server, a TomCat or if you are running from a jar file, it depends on the ClassLoader implementations of the environment you are currently in.
Here is an example that you may use to trace what's going under the hood in your particular case.
I am contributing a proven case that if classpath is, say, all jars in a folder, and you want to prioritize one (or some) of them, this does not work:
Windows:
bin/prioritized.jar;bin/*
Linux:
bin/prioritized.jar:bin/*
It appears that the first path bin/prioritized.jar is ignored just because the second one with a wildcard includes it in its own scope. This is what effectivelly breaks the specified order of classpaths.
Therefore, in order to have multiple resources prioritized (tested on Java 10.0.1), you need to put them in non-overlapping scopes and then they will work.
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)