ClassNotFoundException after replacing a jar in classpath - java

I have a java process running with two jar files in the classpath namely
- A.jar
- B.jar
While the process was running, I replaced the B.jar with another B.jar which I updated with some files. Now in my process, I see some ClassNotFoundException s for the classes in the B.jar. I don't understand what is happening here. I thought the jars would be loaded when the java process was started. If that is the case why is it happening? Can somebody help me with this?? I know if I restart the process, everything will be fine. But I am curious to know the reason behind this.

Classes in a JAR file are loaded when they're first used, not at JVM startup. By replacing B.jar while the application is running, if you've removed classes that are referred to by others, you will get a ClassNotFoundException.
This can also happen in Java 7 if a class that you haven't used for a while has been garbage collected. The JVM will attempt to re-load it, and find that it is no longer in the classpath. This can also happen in earlier versions of Java if you're using the -XX:+CMSClassUnloadingEnabled startup option.

JVM supports static and dynamic loading of classes. JVM will load at startup all classes that are linked explicitely, but won't "discover" classes that are loaded dynamically at Runtime, via reflection for example. If you're doing a Class.forName("org.package.mySuperClass") in your code, and if your SuperClass is never linked by other pieces of code, it will be loaded at call time. If your jar containing this class has been removed from the classpath before the call, a ClassNotFoundException will be thrown
Note that a lot of modern framework use dynamic loading (even dynamic compilation that links to classes in classpath that were not linked before), and it's diffcult (and uncertain) to know which ones.

Related

How Maven loads classes using ClassLoader

My knowledge of ClassLoader in Java at the moment, is a little obscure. That's because I have not found any good documentation geared towards beginner of CLassLoader, thus far. And what I am looking for exactly is in relation to Maven. With disclaimer stated, let me get into my question.
I am writing a Spring MVC application, and I decided to look into how the Dependencies - Jars and classes are loaded using ClassLoader. And what I found is according to the documentation of ClassLoader, classes in Jar are loaded from CLASSPATH, and I can see them under the .m2/repository directory, but CLASSPATH does not yield anything, it's practically empty.
Can somebody please explain to me, how the classes from JAR are loaded into JVM Memory using ClassLoader using Maven, if the CLASSPATH is empty.
Thanks
You're confusing a few things.
The ClassLoader
The ClassLoader is a runtime concept. Maven is a compile time concept. Therefore, one has nothing whatsoever to do with the other. Maven and ClassLoaders do not interact. At all.
When you start a basic java app (java -jar foo.jar or java com.foo.MainClass), you get 2 classloaders. One loader will load system stuff: java.lang.String, for example. the executable itself 'just knows' how to do this (you don't need to configure PATH, CLASSPATH or JAVA_HOME - it just works); up to java 8, it finds rt.jar automatically, which contains String.class and other core classes. Starting from java 11, it finds the jmod files in your java distro.
Then, once the VM has 'booted up', the VM makes another classloader, also based on its built-in stuff: The app classloader.
This one uses 'the classpath'. The source of this depends on how you ran your java app:
java -jar somejar.jar
The source of the classpath in this case is the Class-Path: line in the jar's manifest (the file at META-INF/MANIFEST.MF). And nothing else - the CLASSPATH environment variable, and any -cp or -classpath options are entirely ignored.
java -cp a.jar:b.jar:. com.foo.ClassName
Note that -cp is short for -classpath (they mean the same thing): Here, the classpath is taken to be all the files and directories listed (on windows, use ; as separator instead), and classes are loaded from there. Including com.foo.ClassName itself.
java com.foo.ClassName
If you don't explicitly specify a -cp param, then the environment var CLASSPATH is used. You don't want this though. Always specify classpath.
That's runtime - maven has nothing whatsoever to do with this.
Make your own
You can make your own ClassLoader; the abstraction is such that all it needs to be able to do, is turn a resource name into bytes. That's it. You can make a ClassLoader (literally! public class MyLoader extends java.lang.ClassLoader { ... }) that.. loads data from a network, or generates it on the fly, or fetches it from an encrypted data store. Whatever you like.
Using custom classloaders like this is a solution for finding classes in 'weird' places (not jar files or directories), as well as a mechanism to allow java to 'reload classes' on the fly - very useful when developing, say, web apps, without the use of a hot-code-replacing debugger like eclipse has. ClassLoaders is a mechanism whereby a web server can have the following feature: "I load jars or wars from a certain preconfigured directory.. and if you replace the jar, I will see it and start using the new one".
Writing your own ClassLoader is bordering on rocket science and not usually required unless you're, say, writing an app server. Not a common job.
Maven
To compile source code, the compiler must know the methods and fields and such of all the types you refer. You can't compile "Hello".toLowerCase(); if the compiler doesn't know what String contains.
Thus, compilers also have this notion of 'I need to find classes'. But This is not called class loading, and notably, maven never loads any classes. If it did, any static initializers in any class would run, and mess up your compile. Maven instead just inspects the class file, never letting the VM actually load it, to know what kind of methods and fields and the like are on offer.
java.lang.ClassLoader plays no part in this.
javac itself has a -classpath option as well. So does maven, really.
Maven constructs the classpath automatically, by putting the stuff it already compiled for you (e.g. when compiling the stuff in src/test/java, the compiled stuff from src/main/java is on the classpath), as well as all the dependencies. How? Well, does it matter? Maven does. It constructs a large list of dirs and jars and passes it to javac via the -classpath parameter.

NoClassDefFoundError exception after .jar file moved away

We have a strange issue with two Java processes A and B that load same jar file (not 2 separate copies of the same file, same actual file).
The .jar file was replaced with a newer copy (renamed to .jar_BACK, then a new file copied in its pace), and one of the Java processes (A) restarted. The other Java process (B) keeps running.
Process A works without any problem.
Process B gets a NoClassDefFoundError exception when it tries to use one of the classes in the .jar we updated. The class that is "not found" was already loaded before.
Investigation by jar -x shows that the "missing" class is actually present in both old and new version of the jar, in same location within the jar file and the class itself is unchanged.
Restarting process B fixes the problem.
What would be causing such a behavior?
My current guess is: either standard Java classloader or some custom classloader invalidates classes from a jar when .jar file is renamed. But I can't find any documentation on this, and my previous understanding was that classloader does not do any such monitoring for missing .jar files.
I am pretty sure we're not using any custom class loaders, either.
Java version: 1.8.0_144-b01
OS version: Red Hat Enterprise Linux Server release 6.9 (Santiago)
I believe your issue stems from the fact that you changed the name of the jar and tried to overwrite it with a different file of the same name. The JVM doesn't like that. This question probably has the details you want.

jar built with jwrapper doesn't work

jwrapper manipulates application jars somehow, and is resulting in a non-functioning jar: at runtime it throws a "MyClass cannot be cast to MyClass" type error. I believe this is caused by re-evaluating code that creates a class loader, leading to multiple instances of class MyClass being loaded.
The jwrapper docs don't describe the changes made to the jar, except for the use of pack200. I've tested pack200 in isolation, and it does not cause this problem.
I've also tested the jar built by jwrapper without using the wrapper executable, by passing it to "java -jar". So it's not jvm transmuting, or anything else that the wrapper is doing: the jar itself is broken.
UPDATE:
jwrapper allows skipping pack200, but then the install file is huge. Since pack200 works when run standalone, I could work around this if there were some way to tell jwrapper that the file is already packed. Using <Pack200Exceptions> doesn't help, because then it doesn't know the file is packed.
The underlying problem is that jwrapper sets the pack200 option "modification_time" to "latest", which changes the modification times of all the class files. At run-time this causes the clojure compiler to attempt to recompile the classes from source.
A work-around is to remove the .clj files from the jar prior to packaging, preventing the compiler from running. The lein ":omit-source" option is not sufficient here, because it leaves in .clj files from any dependencies. Instead you must use a pattern in :uberjar-exclusions, e.g.
:uberjar-exclusions [#".(clj|java)"]
as detailed here:
https://github.com/technomancy/leiningen/issues/1357

Resolving runtime dependencies in Java

I've two jars, Jar1 used in compilation and Jar2 is not used at compile time.However both Jar1 and Jar2 are present on the classpath.
I tried finding the references of classes for Jar2 but did not find any of the classes used in the application.
But what if the Jar1 uses some classes of Jar2 at runtime ?
How can I be sure enough to delete the Jar2 from the library and the classpath that it is used by Jar1 at run time ?
Is there any tool available which can tell if any jar is not used in any scenarios at run-time with 100% surety ?
[Little background : I am upgrading to newer version of POI, previously distribution had contrib.jar, now this jar is not present in the recent version. I tried compiling the project after removing the contrib.jar and it compiled successfully. But I want to be doubly sure that removing this from the libraires should not have any adverse effect at runtime.]
Regards,
JE.
1 - About the "dependency-finder" tool:
In theory, each *.class file has a constant pool where the name of dependencies can be found. However, the code could use reflection and make up the name of classes/interfaces dynamically, so finding out if one jar depends on another to work properly is not an easy task at all.
2 - About your particular case:
It seems like, indeed, you don't need such dependency; but if you really want to double check the only way would be to analyse the code of your project by hand. Even if you found a dependency-finder its result wouldn't be 100% reliable because of what I've explained in my first point.

Compiling Java & JARs

I just asked a recent question about distributing executable JARs and their dependencies, and it made me realize that my understanding of JARs may be fundamentally flawed.
Thus, some might say "Hey now! This here is a duplicate question!" But I say nay, this question is a completely separate offshoot of this
original question, and is concerned with Java fundamentals!
If I have an application that depends on, say, the Apache Commons CLI as well as JODA Time, and I pack this app up into a distributable JAR, my original question was: Without including the CLI and JODA JARs in my JAR, how does the program run on the client-side???
I am now thinking that since my code, which uses CLI and JODA, gets compiled into classfiles, and that bytecode is what gets packaged, then there is no need to include CLI or JODA (or any other 3rd party JAR) in my JAR, since it is all now functioning bytecode.
Can someone confirm or correct me? This revelation, though late in coming, has been staggering.
No, that is not quite right. The key to everything is the classpath. Is all of the compiled code and/or other resources on the classpath? If you package everything up in one single jar, then yes, it is in the classpath and the JVM will locate all the resources to run. Otherwise, you need to specify (with a .bat or .sh file or something) all the resources that your application is dependent on, so the JVM will be able to appropriately look for those resources (be they Java code or properties files or whatever).
Also if I am reading your question right, are you assuming that the CLI and JODA code gets compiled into your code? If so, I hate to burst your bubble, but that is not the case. When your code compiles, it does not bring in dependencies (not in the sense you may be thinking). What it does at a conceptual level (correct me if I'm wrong JVM gurus) is it references other classes. Those references are what you are building when you code a class and compile it. At runtime the JVM will attempt to locate the compiled class behind the reference and THAT is where you either need the jar with those classes in the classpath OR you need those classes in your executable jar.
Make sense?
The third party libraries (JodaTime, for example) need to be on the classpath during runtime. Not "packaged within your JAR".
If your app is launched from a JAR. You should specify the classpath in the manifest file which is packaged within the jar - http://download.oracle.com/javase/tutorial/deployment/jar/downman.html
You can have ANT generate the manifest classpath for you using the manifestclasspath element - http://ant.apache.org/manual/Tasks/manifestclasspath.html

Categories

Resources