• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Tim Cooke
  • Devaka Cooray
  • Ron McLeod
  • Jeanne Boyarsky
Sheriffs:
  • Liutauras Vilda
  • paul wheaton
  • Junilu Lacar
Saloon Keepers:
  • Tim Moores
  • Stephan van Hulst
  • Piet Souris
  • Carey Brown
  • Tim Holloway
Bartenders:
  • Martijn Verburg
  • Frits Walraven
  • Himai Minh

Trouble loading JARs at runtime - class dismatches

 
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'm having trouble with my Java application when trying to load other JARs at runtime.
Actually, the JAR is loaded fine, but for some reason when I try to cast an instance of some class T loaded from the external JAR to class MyClass (which it clearly extends), a ClassCastException is thrown. I can guarantee the external JAR is correct due to the next strange fact.
I am developing my main application in Eclipse and usually test it using the editor itself. When I first created the mechanism for runtime JAR loading, it worked fine with main app being launched from Eclipse. However, once I've tried to export it to its own runnable JAR and run it separately I started getting that exception. When I try to launch the very same code from Eclipse it starts working fine again.
I've recompiled both external JAR and main app a dozen times already but the problem does not disappear. I tried checking classes before trying to cast anything and after several tests I got something like "com.mypackage.MyClass != com.mypackage.MyClass"! In the latest test I simply iterate over all the superclasses of the loaded class (it is actually called "ru.windcorp.stackcalc.saga.SAGALibrary") and check each on whether it matches or not the class the loaded class extends in its declaration ("class SAGALibrary extends ru.windcorp.stackcalc.lib.JavaLibrary").
One more insteresting detail: the method that handles JAR loading could not be decompiled by my JavaDecompiler (v.1.0.0).
So, any ideas on what the problem could be?

Exception:


Code:

SAGALibrary declaration:


JavaSteakLoader.load() - the external JAR loader:


Finally, the test code:

...and its output:
 
Bartender
Posts: 1210
25
Android Python PHP C++ Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
If the same class gets loaded by two different classloaders, they are considered as two different classes and casting one to other throws ClassCastException.
In your "while (clazz != null)" loop, can you also print "clazz.getClassLoader()" and "Library.class.getClassLoader()"?
That can tell us which classloader instance is loading each class, and confirm is this is problem.
 
Oleg Shubin
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well, the class loader is different for sure, as I explicitly create a new one before loading the classes (new URLClassLoader( new URL[] { ... } )). I guess I'll have to exclude it somehow, but how do I load the JAR then, if its location is choosen by the user?
 
Karthik Shiraly
Bartender
Posts: 1210
25
Android Python PHP C++ Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Please print the classloader instances as requested to first check if this is indeed the problem. I had seen the URLclassloader in the code, but that doesn't necessarily result in the problem.
There is a concept of classloader hierarchy in JVM and that is the information we want for diagnosing this.
 
Oleg Shubin
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Yes, the class loaders differ (sun.misc.Launcher$AppClassLoader@1f96302 vs. java.net.URLClassLoader@14ae5a5).

Output:


Code:
 
Karthik Shiraly
Bartender
Posts: 1210
25
Android Python PHP C++ Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator


class ru.windcorp.stackcalc.lib.Library == class ru.windcorp.stackcalc.lib.Library: false
class ru.windcorp.stackcalc.lib.Library (sun.misc.Launcher$AppClassLoader@1f96302); class ru.windcorp.stackcalc.lib.Library (java.net.URLClassLoader@14ae5a5)


It looks like "JavaLibrary.class" and "Library.class" are getting included in the plugin JAR file too.
If so, it's unnecessary because they're already packaged in your main application JAR.

One thing that's a mystery to me is that normally URLClassLoader's parent classloader is the application classloader (sun.misc.Launcher$AppClassLoader@1f96302 here).
Check this with clazz.getClassLoader().getParent().
When URLClassLoader is asked to load a class, it first asks the parent CL if the parent has already loaded that class.
In this case, since your load() method already refers to JavaLibrary at compile time, "JavaLibrary.class" and its parent "Library.class" should have already been loaded by application CL.
I'm unable to explain why URLClassLoader loads them again from its own JAR.
There must be some other classloader in the mix or class difference or something else that is not mentioned in this discussion, that is making it do so.

Anyway, try removing "JavaLibrary.class" and "Library.class" from plugin JAR and see if it works.
 
Oleg Shubin
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I've run another test showing the CL hierarchy for each class. Here are the results:

Exported:


From Eclipse (works fine):


Code:


I've checked the plugin JAR and it doesn't contain any classes of the main app (package "ru.windcorp.stackcalc.lib" doesn't exist at all).
I think at this point I'll have to find a solution without class-casting, for example, do all "mainLib.register()" in JavaLibrary constructor, but this doesn't make this class loader stuff less strange...

Perhaps I could just attach the JARs? Maybe I'm doing some a lot more essential thing wrong?
 
Karthik Shiraly
Bartender
Posts: 1210
25
Android Python PHP C++ Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
So, the mystery only deepens

The eclipse hierarchy is exactly what I'd expect should happen. On the other hand, the first one is very odd:


class ru.windcorp.stackcalc.saga.SAGALibrary == class ru.windcorp.stackcalc.lib.Library: false
Class loader: java.net.URLClassLoader@fa79e0; Main class loader: java.net.URLClassLoader@14ae5a5


Library.class is itself being loaded by a 2nd URLClassLoader which is not the one you have instantiated in your code. It seems to be created by the launcher of this application.
On reading the thread again, I see this was the case before too, but I did not notice then that there are 2 different URLClassLoader instances.

How exactly are you executing that first "exported" one? Is it command line or Eclipse Run configuration or what? What cmdline arguments are being passed to it, if any?


 
Oleg Shubin
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well, by exported I mean packaged to a JAR file and launched like an application using the OS, that is, basically double-clicking the JAR. So, yes, the command line thing, totally separated from all development environments. The second, "Eclipse", output is the one I get when launching the app via Eclipse's Run button.
In other words, the first output is what an end user could get by using the distributed application without knowing Eclipse exists at all.
 
Karthik Shiraly
Bartender
Posts: 1210
25
Android Python PHP C++ Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I tried a few classloading experiments with a test application running on Ubuntu, but I'm unable to replicate this loading of "Library.class" in a 2nd URLClassLoader, regardless of whether it's launched from explorer, command line or eclipse.

What all JARs do you have and what classes do they contain? Is it possible that elsewhere in your code the JAR that has "Library.class" is itself being loaded at runtime with another URLClassLoader?


 
Oleg Shubin
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
- StackCalc.jar (main app with JavaLibrary, Library, JavaSteakLoader & stuff)
- SAGA.jar (plugin with SAGALibrary)

Contents of SAGA.jar

SAGA.jar
- META_INF
- - MANIFEST.MF (modified as to contain some load info: "Manifest-Version: 1.0\nSteak-Main-Library: ru.windcorp.stackcalc.saga.SAGALibrary\n")
- ru
- - windcorp
- - - stackcalc
- - - - saga
- - - - - awt
- - - - - - SAGADrawer.class
- - - - - - SAGAPanel.class
- - - - - - SFrame.class
- - - - - commands
- - - - - - SAGACommand.class
- - - - - - CommandCreateFrame.class
- - - - - saga.sfl
- - - - - SAGALibrary.class
- .classpath
- .project

Here's a link to a RAR with compiled JARs and the source code. Could you please take a short look at the files?
To load SAGA run StackCalc.jar, choose Function -> Import JSL... -> select the JAR. System.out is mirrored in tab "Output" while System.err is printed to "Error Output".

...

I'd just like to thank you a LOT for helping me with this problem... I don't know much about how Java operates internally and I don't have much overall programming experience, so I have no idea what I'd be doing without your help.
 
Karthik Shiraly
Bartender
Posts: 1210
25
Android Python PHP C++ Java Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You're welcome! Thank you for bringing an interesting problem.

After analyzing your JARs and trying them out, I've solved it.
It's happening because in Eclipse's "Export Runnable JAR file" wizard, the Library handling option has been set to 2nd option "Package required libraries into generated JAR".
Instead select 1st option "Extract required libraries into generated JAR". That will make this class casting problem go away.

Explanation:
-----------
When 2nd option is selected, Eclipse includes its own JAR loading framework called JarInJarLoader.
You can see this if you open StackCalc.jar with a unzip utility and examine its org/eclipse/ subdirectory and manifest file.
That framework was loading "Library.class" and other classes in its own URLClassLoader.
That option is probably suitable if an app has some dependency JARs which should be retained as JARs inside one big application JAR.
But it's not required for your application.
 
Oleg Shubin
Ranch Hand
Posts: 32
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thank you just so much! That did in fact solve the problem.
Gotta be more careful about extraction options in the future

Looks like I had had once a situation in which I had to actually package my JARs inside other JAR and since then I've been compiling with this stuff.
 
Have you no shame? Have you no decency? Have you no tiny ad?
the value of filler advertising in 2021
https://coderanch.com/t/730886/filler-advertising
reply
    Bookmark Topic Watch Topic
  • New Topic