Thanks for your great work with One-JAR!
To reduce memory usage in an application that I bundle with the One-jar ant task I decided to work from the latest MAIN 0.98 source and make a stab at implementing lazy bytecode loading.
So I downloaded the latest 0.98 source and worked on that.. I didn't realize that the ant jar wasn't being rebuilt at first automatically with a regular ant build, but after that was out of the way I made good progress.
I first fixed the case of classes not being found when the jar is run from a folder containing a space in it.
After a bunch of testing I've come up with a pretty good modified version that implements lazy ByteCode loading. I've still got to do a little more polishing of the code and then I'll post the patch as an enhancement.
I'm using the modified version just in a regular java -jar MyJar.jar situation so I'm not sure if the changes will break anything else, but it seems to work pretty good so far.
JVM Memory usage after a gc() went down from:
56MB using (0.96) down to 18MB using the modified 0.98 version.
This was with preloading bytecode for classes in the main/main.jar and one my other /lib/ jars, other bytecode was lazy loaded.
I've tried to make it flexible so there are new -Done-jar property options to:
- Disable preloading the main jar (on by default)
- Allow preloading resource bytecode eg. non-classes (off by default)
- Pass a comma seperated list of jars to preload.
- Disable lazy loading (enabled by default) so that it acts similar to the old version.
Hopefully I'll get time to polish off the code in the next day or two and post it as a patch.
P. Simon Tuffs
Hi Jamie: just a quick note to say thankyou. Lazy loading is something I've thought about doing but time constraints have prevented. This is such an important enhancement that I'll try to use it to slingshot myself at finishing the 0.98 release.
Let me know when you get the patch in place.
Ok I found some time last night to clean up the code a bit further and added some code to speed up lazy loading.
I changed how the SoftReference map cache works a little bit so that a lazy load of ByteCode from a particular lib jar causes the remaining jar entries (that haven't been already loaded) to be loaded into SoftReferences as well.
This seems to help speed up class loading for non-preloaded jar libs especially if there are many small classes being requested.
I submitted the patch here:
For the future, I'm not sure if there would be a way to speed up jar lookups by keeping a handle to the JarInputStream open? Similar to connection pooling in database land..
After doing some more testing, I realized that in the 0.98 version resources were being loaded by jarfile connections instead of directly via the onejar: handler and ByteCode handling. It was using the FileURLFactory instance instead of the OneJarURLFactory to generate URLs in the getResource() and getResources() methods.
I added a comment to the patch request above that includes a second patch attachment.
It applies on top of the first patch that I submitted.
My application speed now after this second patch seems to be pretty much as fast as when using the original 0.96 version that pre-cached everything.
For anyone that would like to test out the changes for the modified lazy loading version, you should be able to try it out by downloading the current CVS release:
There are a few command line parameters that you can tweak or play around with:
-Done-jar.lazyload.disable=true (disables this patch pretty much, so not sure if you want to test that? :)
-Done-jar.preload.mainjar=false (Disables automatic main/main.jar bytecode preloading, true by default)
-Done-jar.preload.jars.resources=true: (Enables non-class resource preloading, warning will increase memory usage)
-Done-jar.preload.jars=lib/one.jar,lib/sub/two.jar (String containing comma seperated jars to preload, no spaces please. Eg. lib/one.jar,lib/sub/two.jar relative to location contained within the final packaged one-jar.jar)
The patch actually will try to keep as much of the bytecode bytes in memory for quick access on standby, but if memory is an issue any unloaded byte may be subject to garbage collection. Note: the patch uses SoftReferences to hold the unloaded bytes, so the garbage collector will attempt to get rid of any SoftReferences before throwing OutOfMemory errors.
"Pre-loading" specific jars will ensure that the bytes within are loaded and will be retained even if memory is tight.
Hope this patch helps anyone that's operating under tight memory conditions, or has many larger external jars that are only partially used. (that normally might blow up memory usage, even though only a small part of the classes are actually used)