JVM locks JAR files on startup - java

I've implemented a custom ClassLoader which is set via system property -Djava.system.class.loader=com.MyClassLoader. The CL contains a static initializer that invokes some code (before any class is loaded) manipulating the byte code of a class file within a jar (maven dependency) using the javassist library. This works fine, except that i cannot replace the old jar with the new one since the JVM is locking the file and only releases it when it terminates. Why is that and how can I enforce the JVM to release the lock?
Here is a little code snippet:
public class CustomClassLoader extends ClassLoader {
static {
...
modifyJar();
}
private static void modifyJar(){
URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
URL[] urls = urlClassLoader.getURLs();
for(URL url : urls) {
//find matching jar and modify byte code
}
replaceJarFile(metaData);
}
private static void replaceJarFile(JarMetaData jmd){
//add modified class to new jar file
JarFile jar = new JarFile(jmd.getJarFile());
...
//this method call returns false, jar is locked by another process (the JVM)
if(oldJarFile.delete()){
...
}
}
}
OS: Windows 10
JDK version: 1.8.0_131

It has never been specified that changing the jars in use by a JVM should be possible. And the JVM is locking the jars for quiet a long time now. Changing the classes within a jar in use would also bear the semantic problem of how to handle modifications to already loaded classes or even an ongoing class loading overlapping with a write.
Since modifying the jar would be a permanent change, the most reasonable approach for that would be doing it before starting the JVM, e.g. in a different JVM.
But if you want to change class definitions on-the-fly, you should write a Java Agent. For those JVMs supporting Java Agents, the Instrumentation API offers everything needed, e.g. transforming classes at load time or even redefining already loaded classes.
It also offers a standard way of adding jar files to the bootstrap or system class path, whereas assuming that the application class loader is a subclass of URLClassLoader will start to fail with Java 9.

Related

Java ClassLoader - force reloading already loaded classes

I'm currently trying to load classes into my application so that I can then filter out those that don't contain any test / #Test-methods. I want to run these tests in my application afterwards.
So far, so good - except it seems like the URLClassLoader I'm using (or probably any ClassLoader) doesn't actually reload classes that are located on my applications classpath.
More precisely, a user of my application starts by selecting a number of .java source files. Those are then copied to a temporary location and a number of regex match/replace pairs are applied to the copy of the original source files. Next, the copies are compiled, and I then want to load those compiled .class-files into my application so I can run them as tests.
In most cases, this works as desired.
Unfortunately, if the fully qualified class name (FQCN) of any of the selected files is identical to the FQCN of a class that is part of this application (such as tests for this application), the ClassLoader ignores the specified path (to %temp%\myfolder\) and the (updated) version of the class file located there, but instead uses the already present/loaded version of the class.
After some research, this behaviour can be expected according to the docs (emphasis mine):
• The loadClass method in ClassLoader performs these tasks, in order, when called to load a class:
- If a class has already been loaded, it returns it.
- Otherwise, it delegates the search for the new class to the parent class loader.
- If the parent class loader does not find the class, loadClass calls the method findClass to find and load the class.
I tried using findClass, but it's unfortunately not visible.
Hence my question - does anyone know how to force java / the ClassLoader to actually load the class from the specified path, ignoring any - FQCN-wise - identical existing classes?
A classloader first delegates to its parent classloader, which is how it determines "if a class has already been loaded". If you want to force a classloader to load a new class, one way is to insert another classloader in the chain which fails to load/find the same class. Very quick, incomplete example:
class FirewallLoader extends ClassLoader {
public FirewallLoader(ClassLoader parent) {
super(parent);
}
public loadClass(String name, boolean resolve) {
if (name.equals("some.class.Xyz")) {
throw ClassNotFoundException();
}
super.loadClass(name, resolve);
}
}
You make the "FirewallLoader" the parent or your URLClassLoader, and then your URLClassLoader will load new versions of whatever classes the Firewall loader filters out.

Does URLClassLoader use lazy loading?

I'm using URLClassLoader to load jars at runtime from a server URL (let's say: http://jarsToBeLoaded/jars/). I load commons-collection.jar, commons-httpclient.jar and commons-io.jar, for example. My question is: those jars are downloaded as soon as I invoke addURL() for each jar or as soon as I instantiate a class that is used within one of those jars (lazy)?
From what I can tell, the behavior is not specified, so it will depend on your JVM how the URLs are treated. I have looked through the code of Open JDK 8u40. For that implementation there is no downloading of Jars when addUrl() is called. The call is delegated to sun.misc.URLClassPath for which you can see the implementation here:
public synchronized void addURL(URL url) {
if (closed)
return;
synchronized (urls) {
if (url == null || path.contains(url))
return;
urls.add(0, url);
path.add(url);
if (lookupCacheURLs != null) {
// The lookup cache is no longer valid, since getLookupCache()
// does not consider the newly added url.
disableAllLookupCaches();
}
}
}
All the magic with loading classes from your Jar files will happen when you try to load your classes in those jars, using your custom classloader, for the first time and java.net.URLClassLoader.findClass will be called. I'm not sure if the Jars will actually be downloaded and cached somewhere or if the JVM will work with network streams all the time - the URLClassPath code is not easy to follow...

Dynamically loaded Class cannot access Applet loaded Class

My StartApplet is small to keep startup quick.
It then downloads various classes in various jars using (URLClassLoader)getSystemClassLoader().
The problem I am experiencing is that there are several interfaces defined in the StartApplet which are passed to the dynamically downloaded classes using method invoke. I always get class not defined.
It seems the system class loader does not contain any StartApplet loaded classes including the interfaces.
So, I try loading in the interfaces into the systemClassLoader using a downloaded jar but I still get class not defined and I guess this is because the same class has been loaded in twice using difference class loaders and therefore is seen as two difference classes.
I tried loading the downloaded jars using the classloader of one of the interfaces(StartApplet) but there were errors.
I tried forgetting about the system class loader and instead creating a new URLClassLoader using the classloader of the interfaces(StartApplet) as the parant class loader but again errors occurred.
I have tried loading the dynamic jars into Thread.currentThread().getContextClassLoader() but again errors occurred.
My question...
Is there a way to dynamically load classes in jars using (URLClassLoader)getSystemClassLoader() and allow them to see/access and use the classes that have already been loaded by the instantiating applet??
some code example would be really nice.
Many Thanks.
The crux is the system class loader doesnt reference the applet class loader.
The applet cannot start with any external jars so whatever classes it passes have to be loaded in with the applet.
I just need the dynamically loaded classes in the systemclassloader to be able to use the classes loaded with the applet.
please help.
ps. here are some snipets...
private void addPath(String _path)
{
try{
File f=new File(_path);
if(!f.exists())return;
if(!f.isDirectory())return;
Method method=SYSTEM_CLASS_LOADER_CLASS.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(SYSTEM_CLASS_LOADER,new Object[]{f.toURI().toURL()});
}catch(Throwable _t){
handle(_t);
disconnect();}
}
private void addLibrary(String _name)
{
try{
Method method=SYSTEM_CLASS_LOADER_CLASS.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(SYSTEM_CLASS_LOADER,new Object[]{ClassLoader.getSystemResource(_name)});
}catch(Throwable _t){handle(_t);}
}
SYSTEM_CLASS_LOADER=(URLClassLoader)ClassLoader.getSystemClassLoader(); // DOESNT WORK
SYSTEM_CLASS_LOADER=(URLClassLoader)MyInterface.class.getClassLoader(); // DOESNT WORK
SYSTEM_CLASS_LOADER=(URLClassLoader)Thread.currentThread().getContextClassLoader(); // DOESNT WORK
private void callWithInterface(MyInterface _myI)
{
Class<?> myClass=Class.forName("dynamic.MyClass",true,SYSTEM_CLASS_LOADER);
Constructor<?> myConstructor=myClass.getConstructor();
Object myInstance=myConstructor.newInstance();
Method m=myClass.getMethod("MyTest",new Class<?>[]{MyInterface.class});
String s=(String)m.invoke(myInstance,new Object[]{_myI});
}
last line causes...
Thread=Thread[Thread-17,4,http://MyDomain/-threadGroup]
java.lang.ClassNotFoundException: MyInterface
java.net.URLClassLoader$1.run(-1)
java.net.URLClassLoader$1.run(-1)
java.security.AccessController.doPrivileged(-2)
java.net.URLClassLoader.findClass(-1)
java.lang.ClassLoader.loadClass(-1)
sun.misc.Launcher$AppClassLoader.loadClass(-1)
java.lang.ClassLoader.loadClass(-1)
java.lang.Class.forName0(-2)
java.lang.Class.forName(-1)
StartApplet.run(23759)
java.lang.Thread.run(-1)
I have figured it out..
The problem I had was caused by a jar name conflict causing the required classes to fail at loading. Once I realised this and corrected the problem I successfully enabled the dynamically loaded classes to access the applet loaded classes by loading the dynamically loaded classes using the applet class loader instead of the system class loader.
I modified my code using the following lines and other adjustments to suit...
MyDynamicClassLoader=new URLClassLoader(new URL[0],MyAppletLoadedInterface.class.getClassLoader());
method.invoke(MyDynamicClassLoader,new Object[]{MyDynamicClassLoader.getResource(DynamicJarName)});
MyDynamicClassLoader now holds references to all applet loaded classes and dynamically loaded classes with the ability to reference each other. For some reason the system class loader does not hold the applet loaded classes.
Regards
Penny

Adding jar file to classpath at runtime without reflection tricks

There are several posts about how to add jar-file to classpath at runtime by following idea:
- get current system classpath;
- suppose it is URLClassLoader;
- use reflection to set access for protected addURL method;
- use mentioned method to add url to classpath.
Here is an example:
Adding files to java classpath at runtime
Because of 2 and 3 steps this looks like "nasty hack".
How could I extend URLClassLoader and set it as a current? I am bit confused with classloaders and tried the following:
public static void main(String... args) {
URLClassLoader loader = new URLClassLoader(new URL[]{new URL("file:jxl.jar")});
System.out.println(loader.loadClass("jxl.Workbook"));
Thread.currentThread().setContextClassLoader(loader);
System.out.println(Class.forName("jxl.Workbook"));
} // main
I get ClassNotFoundException on the fourth line, while second works ok. (why it is so, by the way?)
The Class.forName method uses the "defining class loader of the current class," not the thread context classloader. In your case the ClassLoader that Class.forName will use is the one that loaded your application, i.e. the system class loader. This is a class loader that looks for resources in the class path.

Load Java classes based on a classpath in a properties file

My application uses JDBC database drivers. I load these from a jar file, db2jcc.jar in the case of DB2 which I'm currently working with. With this jar in the classpath, everything is fine, but I have a requirement to find the jar from a property in the application's config file instead - for example,
database.driver=/opt/IBM/db2/V9.5/java/db2jcc.jar
I can load the class via a URLClassLoader ok, but the problem is that I need to treat the object thus created as an explicit DB2XADataSource. For example:
URLClassLoader dbClassLoader = new URLClassLoader(new URL[]{driverJar});
xaClass = dbClassLoader.loadClass("com.ibm.db2.jcc.DB2XADataSource");
DB2XADataSource dataSource = (DB2XADataSource) xaClass.newInstance();
dataSource.setCurrentSchema(DATABASE_SCHEMA); // <- dataSource has to be a
dataSource.setDatabaseName(DATABASE_NAME); // DB2XADataSource to do this
(rearranged and renamed somewhat; I actually do the loadClass in the constructor of the class that contains this code, while the newInstance is in one of its methods.)
I guess I'm getting into a classloader tangle because the classloader that loaded my class is trying to find DB2XADataSource in order to do the cast, but the URL classloader is not above it in the tree. The trouble is, it being long after I should have stopped working for the day (here in the UK) I can't think how best to solve it in a vaguely neat and sane manner.
Ideas?
Thanks.
The simplest approach is to just use the java.beans API (or direct reflection if you must) to invoke the setter methods.
Alternatively: Your database code requires to link to the dynamically loaded code. Therefore, dynamically load your database code. How much is up to you. You might load almost everything except the "bootstrap".
Yep - the class can't load its own dependencies. You could do some ClassLoader magic, but I imagine it would get messy very quickly.
One way to reduce the amount of reflection would be to put any code that depends on DB2XADataSource into an implementation that is invoked via an interface available to the calling ClassLoader.
//in mydb2driver.jar
public class MyDb2Driver implements IDriver {
private DB2XADataSource dataSource = new DB2XADataSource();
public void init() {
dataSource.setCurrentSchema(DATABASE_SCHEMA);
}
//etc.
}
This is loaded with your driver:
database.driver=/opt/IBM/db2/V9.5/java/db2jcc.jar:/foo/mydb2driver.jar
Invoking code is in the regular classpath:
public interface IDriver {
public void init();
//etc.
}
...
URLClassLoader dbClassLoader = new URLClassLoader(new URL[]{driverJar});
xaClass = dbClassLoader.loadClass("foo.MyDb2Driver");
IDriver dataSource = (IDriver) xaClass.newInstance();
dataSource.init();

Categories