35

To speed up the startup time of the JVM, the Sun developers decided it is a good idea to precompile the standard runtime classes for a platform during installation of the JVM. These precompiled classes can be found e.g. at:

$JAVA_HOME\jre\bin\client\classes.jsa

My company currently develops a Java standalone application which brings its own JRE, so it would be a fantastic option to speed up our application start time by adding our own application classes to this jsa file, too.

I don't believe the JSA file was created by magic, so: How is it created? And how can I trick the JVM into incorporating my own classes?

EDIT: I already found out the following:

The classes.jsa is created by the command

java -Xshare:dump

The list of classes to incorporate in the dump can be found in $JAVA_HOME/jre/lib/classlist.

I even managed to add my own classes here (and to add them into the rt.jar for java to find them), and to generate my own checksum below the classlist file.

The final problem is: Only classes in the packages java, com.sun, and org.w3c seem to be recognized, if I leave the same classes in their original packages, they won't be loaded. I searched the whole OpenJDK source for pointer about this, but it seems to have something to do with protection domains. If someone is interested enough in this topic and knowledgeable enough, please add some pointers for me to investigaete further.

Pacerier
  • 86,231
  • 106
  • 366
  • 634
Daniel
  • 27,718
  • 20
  • 89
  • 133
  • Can you share how you recalculated the checksum? – coppit Sep 05 '13 at 17:05
  • 2
    I found my answer in a slide deck for embedded Java -- there's a utility called AddJsum in the openjdk source tree. http://www.oracle.com/technetwork/jp/ondemand/java/20110519-java-a-1-greg-400531-ja.pdf – coppit Sep 06 '13 at 04:29
  • Thanks, I might try this out when I get the time. – Daniel Sep 06 '13 at 09:05
  • 1
    further to @coppit, it's pages 61-62, and tools link: http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/tip/make/tools/src/build/tools – 13ren Sep 19 '13 at 11:09
  • @Daniel, Is this even a portable solution? – Pacerier Aug 27 '14 at 04:31
  • @Pacerier: Since the file contains different classes on different platforms you would have to create it in the install phase of your application anyway, so the method seems portable, the created file itself is mot likely to be not. – Daniel Aug 28 '14 at 04:49

4 Answers4

12

You were almost there, you only need a couple steps to make it work. To add your own classes to the clients.js you need the following steps:

  1. The qualified name your classes (you have it)

  2. The classpath of these classes (you have it)

  3. Know how to recalculate the checksum (you have it)

  4. Dump the new file, providing the classpath of the classes you are now precompiling with the Java classes.

  5. Run the program, providing the same classpath that you used to dump the new classes.jsa

To provide the classpath where are the classes you are adding to the classlist, use the -Xbootclasspath/a command. It will append the directories/JARs when JVM is searching the places where the boot classes are. The default space for the classes.jsa is quite small, if you need to improve it you can use the -XX:SharedReadWriteSize and -XX:SharedReadOnlySize commands. Your dump command you look similar to this:

java -Xshare:dump -Xbootclasspath/a:C:/myfiles/directoryA/;C:/myfiles/directoryB/;C:/myJars/myJar.jar;

The last step is just run the java application normally, rememebering of turn on the share mode. You also need to add the Xbootclasspath excatly as you added on the dump. It will look similar to this:

java myapp.java -Xshare:on -Xbootclasspath/a:C:/myfiles/directoryA/;C:/myfiles/directoryB/;C:/myJars/myJar.jar;

Now every class that you put on the classlist is being shared with other instances running in the same JVM.

Pacerier
  • 86,231
  • 106
  • 366
  • 634
Daniel Pereira
  • 2,720
  • 2
  • 28
  • 40
  • Sounds great. I will definitely check this out! – Daniel Feb 06 '13 at 07:29
  • I'm trying this and failing. I've created a new classfiles file and added the checksum, and specified -Xbootclasspath/a:/path/to/jars/*. The resulting classes.jsa file is the same size as the original. Is it possible that as Daniel suggested, the -Xshare:dump will only dump classes from specific packages? The reason I'm interested in this is that we use a lot of 3rd party libs that are bloating our RSS memory usage. It would be nice to get these shared between JVMs like the standard library is. (And yes, I've seen the cautions in the linked questions. ;) ) – coppit Sep 06 '13 at 05:17
  • Are you replacing the `classlist` file with your modified version? – Daniel Pereira Sep 09 '13 at 10:16
  • @DanielPereira, Isn't the share mode turned on by default? – Pacerier Aug 27 '14 at 04:34
  • 1
    @Pacerier the default behavior is `-Xshare:auto`. There is a difference between `auto` and `on`: `auto` will try to execute with shared mode on, but if it can't be done then it will turn it off and then execute the application normally. If you define `on` it will force share mode to execute and make your application fail to execute if something is wrong. – Daniel Pereira Aug 27 '14 at 13:52
11

As of Java 8u40 (and Embedded Java 8u51), Java now supports Application Class Data Sharing (AppCDS) (ie your own classes in the shared archive). On our embedded java, we've found a startup improvement of >40%! Pretty awesome for almost no work on our part...

https://blogs.oracle.com/thejavatutorials/entry/jdk_8u40_released

Ben
  • 4,785
  • 3
  • 27
  • 39
  • Exactly what I wanted. Sadly it's only available as a commercial feature. But at least it seems to work. – Daniel Oct 22 '15 at 14:44
  • Sadly, Oracle only kept AppCDS feature in for Embedded for version 8u51...we've been trying to get an answer about if/when it will come back from Oracle...but no luck. We got a half answer about "Wow - our product team would love to know your experience with it", but then we never got a response on how to be in touch with them. – Ben Apr 28 '16 at 13:37
8

Interesting idea. As I read it though, it's used for sharing data across VMs and for speeding up classloading, not compiling. I'm not sure how much of a boost you would get, but it might be worth a try if you have a big lag at startup already (though the VM already tries to mitigate that).

As for trying it yourself, it appears this file is normally created when the Sun VM is installed, but you can also control it. Some details are in this older Sun Java 5 Class Data Sharing document (which you may have already seen?). Some Sun Java 6 docs also mention it a few times, but don't add much to the documentation. It seems it was originally an IBM VM feature. And, to continue the link dump, it's explained a bit in this article.

I don't personally know much about it, so I don't know how you might control it. You can regenerate it, but I don't think it's intended for you to put custom stuff into. Also, even if you can "trick" it, that would probably violate a Sun/Oracle license of some sort (you can't mess with rt.jar and redistribute, for instance). And, all that said, I doubt you would see a serious improvement in startup time unless you have thousands or tens of thousands of classes in your app?

(And this isn't really an answer, I know, but it was too big to fit in a comment, and I found the question interesting so I investigated a bit and put links here in case anyone finds the same info useful.)

Charlie Collins
  • 8,806
  • 4
  • 32
  • 41
  • +1 Thanks very much for your great effort. I will read through your links and hopefully come back with a working solution. In addition I didn't know that I wasn't allowed to fiddle with the rt.jar and redistribute it, but I wouldn't care for this anyway, because the only app using it would be mine. – Daniel Jan 14 '11 at 17:30
0

It took a little figuring out but I have 4 Java8 VMs (version 1.8.0_162) running using shared classes. The following script was used to set up and test sharing and with a little modification could be used elsewhere:

#!/bin/bash

# Libraries to load
LIBS1="./lib/protobuf-java-2.6.1.jar:\
./lib/jetty-server-9.2.18.v20160721.jar:./lib/jetty-util-9.2.18.v20160721.jar:./lib/servlet-api-3.1.jar:./lib/jetty-http-9.2.18.v20160721.jar:./lib/jetty-io-9.2.18.v20160721.jar:\
./lib/derby.jar:\
./lib/json-simple-1.1.1.jar:"
LIBS2=":./lib/GTFS.jar"

# Uncomment these lines for the first phase where you are determining the classes to archive. During this phase aim to get as many classes loaded as possible
# which means loading a schedule and retrieving the stop list and next vehicle information
#
#APPCDS="-Xshare:off -XX:+UnlockCommercialFeatures -XX:+UseAppCDS -XX:DumpLoadedClassList=../GtfsAppCds.lst"
#java -Xmx512m $APPCDS -Dderby.system.home=database -classpath $LIBS1$LIBS2 com.transitrtd.GtfsOperatorManager

# Uncomment these lines when the class list is created and run to create the shared archive. Classes marked as unverifiable will need to be removed from the
# archived class list in GtfsAppCds.lst and the lines below run again. LIBS2 above contains jars which are left out of the archive. These are jars which change
# frequently and would therefore cause the archive to be frequently rebuilt.
#                                                                                                                                                                                                               
#APPCDS="-Xshare:dump -XX:+UnlockCommercialFeatures -XX:+UseAppCDS -XX:SharedClassListFile=../GtfsAppCds.lst -XX:SharedArchiveFile=../GtfsAppCds.jsa"
#java -Xmx512m $APPCDS -classpath $LIBS1

# Uncomment these lines when wishing to verify the application is using the shared archive.
#
#APPCDS="-Xshare:on -XX:+UnlockCommercialFeatures -XX:+UseAppCDS -XX:SharedArchiveFile=../GtfsAppCds.jsa -verbose:class"
#java -Xmx512m $APPCDS -Dderby.system.home=database -classpath $LIBS1$LIBS2 com.transitrtd.GtfsOperatorManager

Note that the shared archive file (i.e.the jsa file) is architecture dependent and will need to be built on each target platform type.

Also if a jar uses sealed packages a security exception is thrown, see

https://docs.oracle.com/javase/tutorial/deployment/jar/sealman.html

for information on sealed packages. This was the case above with derby.jar but the problem could be solved by unpacking the jar file, replacing Sealed:true with Sealed:false in the manifest and repacking it.

jars built with older versions of java cannot be used in a shared archive, in the case above the derby version needed to be upgraded from 10.10 to 10.14 to benefit.

paulh
  • 314
  • 2
  • 14