codecentric

Useful JVM Flags – Part 1 (JVM Types and Compiler Modes)

Modern JVMs do an amazing job at running Java applications (and those of other compatible languages) in an efficient and stable manner. Adaptive memory management, garbage collection, just-in-time compilation, dynamic classloading, lock optimization – just to cite some of the magic that happens between the scenes but rarely affects the average programmer directly. At run time, the JVM optimizes the way it handles the application or parts of it, based on continuous measurements and profiling.

Even with such a level of automation (or rather, because of so much automation, as one might argue), it is important that the JVM still provides adequate facilities for external monitoring and manual tuning. In case of errors or low performance, it must be possible for experts to intervene. Incidentally, aside from all the magic that happens under the hood, a wide range of manual tuning knobs is one of the strengths of modern JVMs as well. Of particular interest are the command line flags that can be passed to the JVM at startup. Some JVMs provide several hundreds of these flags, and it is easy to get lost without proper knowledge in this area. The goal of this blog series is to highlight the most relevant flags for everyday use and explain what they are good for. We will focus on the Sun/Oracle HotSpot JVM as of Java 6, though in most cases similar flags exist for the other popular JVMs.

-server and -client

There are two types of the HotSpot JVM, namely “server” and “client”. The server VM uses a larger default size for the heap, a parallel garbage collector, and optimizes code more aggressively at run time. The client VM is more conservative, resulting in shorter startup time and lower memory footprint. Thanks to a concept called “JVM ergonomics”, the type of JVM is chosen automatically at JVM startup time based on certain criteria regarding the available hardware and operating system. The exact criteria can be found here. From the criteria table, we also see that the client VM is only available on 32-bit systems.

If we are not happy with the pre-selected JVM, we can use the flags -server and -client to prescribe the usage of the server and client VM, respectively. Even though the server VM was originally targeted at long-running server processes, nowadays it often shows superior performance than the client VM in many standalone applications as well. My recommendation is to choose the server VM by setting the -server flag whenever performance in the sense of shorter execution time is important for an application. A common gotcha: On 32-bit systems, a HotSpot JDK is required to be able to run the server VM at all – the 32-bit JRE only ships with the client VM.

-version and -showversion

How do we know which Java installation and which JVM type is used when we call java? With more than one Java installation on a system there is always a slight risk of running the wrong JVM without noticing it. Especially popular in this respect are pre-installed JVMs on various Linux distributions, even though I have to admit that things have become better over the years.

Luckily, we have the -version flag available, which prints some information about the used JVM to stdout. One example:

$ java -version
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)

The output shows the Java version number (1.6.0_24) and the exact build ID of the JRE used (1.6.0_24-b07). We also see the name (HotSpot), the type (Client) and the build ID (19.1-b02) of the JVM. In addition to that, we learn that the JVM runs in mixed mode. This mode of execution is the default mode of HotSpot and means that the JVM dynamically compiles byte code into native code at run time. We also learn that class data sharing is enabled. Class data sharing is an approach that stores the system classes of the JRE in a readonly cache (in a jsa file, “Java Shared Archive”) which is used as a shared resource by the classloader of all Java processes. Class data sharing may be beneficial to performance when compared to reading all the class data from jar archives over and over again.

The -version flag terminates the JVM immediately after printing out the above data. However, there is a similar flag -showversion which can be used to produce the same output but then proceed and execute a given Java application. Thus, -showversion is a useful addition to the command line of virtually every Java application. You never know when you suddenly need some information about the JVM used by a particular (crashed) Java application. By adding -showversion on startup, we are guaranteed to have this information available whenever we may need it.

-Xint, -Xcomp, and -Xmixed

The two flags -Xint and -Xcomp are not too relevant for our everyday work, but highly interesting in order to learn something about the JVM. The -Xint flag forces the JVM to execute all bytecode in interpreted mode, which comes along with a considerable slowdown, usually factor 10 or higher. On the contrary, the flag -Xcomp forces exactly the opposite behavior, that is, the JVM compiles all bytecode into native code on first use, thereby applying maximum optimization level. This sounds nice, because it completely avoids the slow interpreter. However, many applications will also suffer at least a bit from the use of -Xcomp, even if the drop in performance is not comparable with the one resulting from -Xint. The reason is that by setting -Xcomp we prevent the JVM from making use of its JIT compiler to full effect. The JIT compiler creates method usage profiles at run time and then optimizes single methods (or parts of them) step by step, and sometimes speculatively, to the actual application behavior. Some of these optimization techniques, e.g., optimistic branch prediction, cannot be applied effectively without first profiling the application. Another aspect is that methods are only getting compiled at all when they prove themselves relevant, i.e., constitute some kind of hot spot in the application. Methods that are called rarely (or even only once) are continued to be executed in interpreted mode, thus saving the compilation and optimization cost.

Note that mixed mode also has its own flag, -Xmixed. With recent versions of HotSpot, mixed mode is the default, so we don’t have to specify this flag anymore.

Let us consider the results of a simple example benchmark which fills a HashMap with objects and then retrieves them again. For each benchmark, the execution time shown is the average time over a large number of runs.

$ java -server -showversion Benchmark
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, mixed mode)
 
Average time: 0.856449 seconds
$ java -server -showversion -Xcomp Benchmark
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, compiled mode)
 
Average time: 0.950892 seconds
$ java -server -showversion -Xint Benchmark
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, interpreted mode)
 
Average time: 7.622285 seconds

Of course there are also benchmarks that show -Xcomp to be best. Still, and especially for long running applications, I would strongly advise everyone to stick to the JVM default settings and let the JIT compiler make full use of its dynamic potential. After all, the JIT compiler is one of the most sophisticated components of the JVM – in fact, the recent advances in this area are the major reason why Java is not considered slow anymore nowadays.

  • Facebook
  • Delicious
  • Digg
  • StumbleUpon
  • Mister Wong
  • Reddit
  • Instapaper
  • Technorati
  • Blogger
Patrick Peschlow

 

Weitere Beiträge aus dem codecentric Blog

5 Responses to Useful JVM Flags – Part 1 (JVM Types and Compiler Modes)

  1. Mirko Novakovic says:

    Super Artikel! -Xint und -Xcomp kannte ich noch nicht…

  2. TJ says:

    Little lost – I thought Java compiler converts source code into bytecode which gets executed by JVM at runtime.
    So there is only byteCode available to JVM.What do you mean by -Xint only interpreted?
    Are you implying times JVM tries to understand the bytecode directly without JIT compiler kicking in?
    please elabrate?

    • Hi TJ,

      yeah, you are on the right track. The input to the JVM is bytecode, and then there are several choices how it handles the bytecode during program execution. At times the JVM interprets bytecode only, without compiling it into native code first. In fact, every Java program you run will normally have some fraction of interpreted bytecode during its execution.

      Early JVMs only contained an interpreter, so your whole Java program bytecode was interpreted only. That’s the main reason why Java was (rightly) considered slow in its early years. Today, modern JVMs still allow you to use interpreted-only mode by specifying -Xint on the command line. Just add -XX:+PrintCompilation to the command line in addition to -Xint and you can see yourself that no compilation of bytecode into native code will happen. Compare the output with a run of the same Java program without -Xint.

      When the JVM started to support bytecode-to-native compilation, people realized that it does not make sense to blindly compile each and every method into native code. Instead, the concepts/technologies nowadays known as “Just-in-time-compilation” and “HotSpot” were developed. In a nutshell, after startup the JVM first of all interprets the bytecode and then during program execution decides which methods to compile into native code. The idea is that only “hot” methods are worth the compiling/optimization effort required to produce efficient native code. On the contrary, “cold” methods will be handled in interpreted mode until they become “hot” – which might never happen for some methods.

      By the way, when you dynamically reload classes or instrument methods at run time, the new bytecode will usually also be interpreted for a while even if the old version was already compiled. So, for every new piece of bytecode it sees, the JVM normally takes a while to decide whether it is “hot”.

      This means that you will indeed find bytecode interpretation with every modern JVM execution. If you would like every method to be compiled into native code the first time it is called, you could specify -Xcomp on the command line, but I cannot really recommed this approach. JVMs are pretty clever nowadays!

      Thanks for your comment. I should really consider publishing an English translation of the series :-)

  3. joe news says:

    It is definitely a fantastic and valuable bit of info. I am satisfied you shared this helpful info with us. Please keep us all up-to-date this way. Thanks for expressing.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>