The Legend of Long JVM Startup Times

Eric Normand · Updated January 21, 2019

One of the most common complaints about the JVM is the long startup
time. People running their apps can sometimes wait up to a couple of
minutes just to be able to start their servers. People complain
regardless of
language. Java programmers complain, JRuby programmers complain, and Clojure programmers complain.

Why is JVM startup so slow?

The easiest answer is that startup time has never really been a big
focus. Most JVMs run on servers for a long time. So most installations
don't care if it takes a few more seconds.

What does the JVM do when it starts up?

There are three things the JVM does when it starts.

It grabs a bunch of memory for the initial heap.

It loads in the classfiles (bytecode) it needs to run.

It initializes needed classes. Some classes do quite a lot.

Then it can run your code. Now, this is complicated (or simplified?) a
little because the JVM will lazily load and initialize classes as they
are needed. But, still, the classes can't be used until they're
initialized. And very often you have to initialize quite a lot of
classes even for the simplest programs.

But is it really slow?

I'm all for experimentation for questions like this. Just like we can
work at the REPL to understand our Clojure code's behavior, we can
similarly do some benchmarking in our terminals.

Let's start with a super simple Java program that does nothing, in the
file Nothing.java.

class Nothing {
public static void main(String[] args) {
}
}

I'll compile it with javac Nothing.java then run it:

> time java Nothing

real 0m0.101s
user 0m0.083s
sys 0m0.021s

I've run it several times and it's always very close to this answer.

That's actually pretty fast. Starting the JVM, grabbing the memory for
the heap, and then exiting takes one tenth of a second.

When I try to change the heap size by passing command line arguments,
it seems to take longer by a few milliseconds. Perhaps just parsing
the arguments adds time. It doesn't seem to matter how big I make the
heap. Even a 4GB heap takes the same amount of time.

I can count the number of classes being loaded like this:

java -verbose:class Nothing | grep Loaded | wc -l

I get:

429

So just to do nothing, we're loading 429 classes! That's good to
know. So that's probably a lower limit.

Running Clojure

Let's do the same test with a minimal Clojure example. Luckily,
Clojure lets us pass in a program on the command line. So we don't
even need a file. This simplest program is just nil.

Wow! So that is fast. Just under a second. It's back down to around
the same time as not using Leiningen. I am going to set that flag in
my .bash_profile!

Running code

So far, we're not running any interesting code. But it's clear that
startup times will only go up if you've got code in your system. And
real applications do a lot of stuff at startup: initializing logging,
setting up the server, reading in configuration files, etc.

If you're running Clojure files, they'll have to be parsed and
compiled. Any required namespaces will have to be read in, parsed,
and compiled as well. AOTing your code will definitely help by
eliminating the parsing and compilation.

Conclusions

Running Clojure takes a lot of time above the JVM startup time. It has
to load a lot of classes. Different setups take more and less
time. But it looks like the minimum on my machine is about one second
when using Clojure.

And that brings up a good point: it tooke me about 30 minutes to test
all of these variations and figure out which commands were faster and
which slower. You can do this, too!

In fact, others have done similar
things. Alex Miller analyzed a lot of different
variations. Nicholas Kariniemi analyzed where time went when booting
Clojure. Joe Kutner analyzed JVM startup times for Spring. And the JRuby Wiki has some information about making JRuby boot faster.

Now, I hope this shows that the JVM is a fine platform. I hope you
want to learn more about it, and how to make use of it with
Clojure. I've got a course teaching all the JVM stuff I
learned from ten years of experience that I still use as a Clojure
programmer. You can also get it as part of a membership, along
with all of the other courses.

What is clear is that Clojure (or JRuby or Spring) are what
take a long time to load, not the JVM. Clojure can take 7-9x the time
the entire JVM takes. So how do Clojure programmers deal with long
startup times? We'll touch on that next time!

Footer CTA

Get the newsletter for free

The PurelyFunctional.tv Newsletter is a weekly email to inspire functional programmers.

Enter your email address to receive emails about Clojure and Functional Programming. These include the weekly newsletter and other great offers. You can unsubscribe any time.