Few weeks ago I got an email message from my colleague Craig Bryant asking about potential fix for an annoying pause during startup of his Java program that did some crypto during initialization. He also attached his code, pin-pointing the problem to the first invocation of the call Cipher.getInstance("DES/CBC/PKCS5Padding")
, which took more than 5 seconds to execute on an HPUX box with JDK1.4.1. Agreed that 5 seconds delay at startup is not a big deal, but is still unacceptable in most development and production systems.
I compiled and executed the program on my old and rusty W2K desktop (a Pentium 350MHz box), and confirmed the delay. I also found that the delay was cut by almost half by just switching from J2SDK1.4.1 to J2SDK1.4.2. Not surprising given the impressive optimizations reported in J2SE 1.4.2. Running the program on a more modern machine (say, a 2.0GHz box) further reduced the delay to a more acceptable sub-second range. I attributed this to slow-but-necessary security intialization overhead and forgot about it.
Craig later reported that he profiled the code and found that most of the delay was due to signature verification. This made sense. Invocation of Cipher.getInstance("DES/CBC/PKCS5Padding")
would require execution of classes in a JCE provider and the JCE engine requires the provider to be signed by a certificate issued JavaSoft. It appears silly, at least for a standalone Java program running on a trusted machine, to verify the JCE provider jar every time the program starts.
Lateron, after reading the recommendation to run the server JVM for better performance (which made sense anyway, as the original program was to run as a server program), I tried the same program with java -server, only to find that startup delay was worse than without -server option, with a factor of 3.5! (a 0.9 sec. delay became a 3.2 sec. delay). Again, an explanation is not hard to come by: -server option causes the JVM to compile all the code used in verifying the signed jar to native code, even if the verification takes place only once. And because of this, the performance gain of running native machine instructions do not offset the time spent in doing the byte code to native code translation.
Although the previous story is in the context of crypto operations, the general observation that signed jar files introduce human perceptible startup delays, applies to many more situations. This delay is proportional to the size of the signed jar and not the size of classes that get used from this jar file. For example, the following HelloWorld.java program:
//File: HelloWorld.java
import lib.HelloWorldLib;
public class HelloWorld {
public static void main(String[] args) throws Exception {
HelloWorldLib.helloWorld();
}
}
------------------------------------------------------
// File: lib\HelloWorldLib.java
package lib;
public class HelloWorldLib {
public static void helloWorld() throws Exception {
System.out.println("Hello, World!");
}
}
takes around 280 milli seconds to execute when class lib.HelloWorldLib
is packaged within a jar file. However, the execution time jumps to 700 milli seconds when this jar file is signed. Adding this class to a large existing jar file such as rt.jar
(which is a 25MB monster!) and signing the resulting jar makes the execution time to be more than 7 seconds. Note: All measurements reported in this paragraph were made on a AMD Athlon 900MHz box running W2K, J2SE 1.4.2 and server JVM. The measurements of first few executions were discarded to allow for OS and filesystem cache warmup.
This is something to keep mind if you plan to deliver signed jar files!