Java "Hangs", and how to combat them...

A recent discussion on the KYOSS list has made me yet again aware that some people still consider Java to be "slow". And from my position, deep inside of the Java ecosystem, I am always shocked by this attitude... Then, I stop and think about some of the complexity involved with properly writing/tuning a Java application; especially large enterprise Java applications. So, in an effort to help, I thought that I might post here some tips and links to how to control some of the bad behaviors which can be encountered.


First, to better understand the problems someone might experience, let's discuss how Java handles memory... Unlike C/Fortran/C++, Java uses "automatic" memory management... This means that the developer doesn't worry about "new" and "free" and allocation of space for the application... The Java Virtual Machine (JVM) handles this for you... 

When the JVM starts up, it allocates a large chunk of physical memory and dedicates it to what is known as the "Heap". Think of the Heap as where Java has decided to keep items in a memory in a very well organized manner. Everything in the Heap is managed by the JVM and goes through stages of it's lifecycle... Items which are short-lived and pass out of scope quickly get garbage collected (GC) (freed up) every few seconds (Eden Space). Items which remain in scope longer than one GC cycle enter the Survivor Space. As items live longer they can be promoted into the "Tenured" and "Permanent" generations (PermGen). But even the PermGen gets garbage collected (http://goo.gl/oRgbTV). 

Since the beginning, one of the main improvements to the JVM and Java has been improvements in the garbage collection subsystem. That's why there are about 5 implementations which can be chosen and they each have a number of runable parameters for when you start your application. Choosing a GC implementation is non-trivial and requires an reasonable understanding of what your application does and how. By default, older Java VMs used a "Serial" GC which pauses the entire JVM while it does it's work. If you have a LOT of objects and a lot of churn, this can be a source of "hangs". More recent JVMs (Java 1.7 and 1.8) use parallel threaded GC mechanisms which still pause parts of the VM, but much less noticeably and for much shorter time periods.

So, how can you as a user try out different GC implementations and parameters? Well, there are lots of web sites out there which have varying levels of information, but a good place to start is with the Oracle GC overview: http://goo.gl/SLAOP3

Something to consider is that with Java 8, The "G1" garbage collector is now the default. Java 8 also no longer has a "Permanent Generation" (PermGen). So, upgrading to Java 8 to run your application may be enough to improve most application's performance. Another "quick fix" could be to use the "-server" flag when starting the application (Usually set in the JAVA_OPTS environment variable), which will cause the JVM to optimize for a lot of server-specific defaults.

The size of the Heap also has a big impact on how garbage collection is handled by the system... By setting a large enough heap for your application without giving the JVM too much memory to have to manage you can limit GC pauses and improve performance and memory usage. The easiest way to see if your Heap size is correct is to use the jconsole application to attach to the running process and inspect the Heap usage, view GC information, and even get stats on lengths of GC pauses. Instructions on using jconsole can be found at: http://goo.gl/IBeoac

Once you have investigated your Heap setup, you will then want to change the Heap parameters for your application. The parameters are as follows:

-Xms - Starting Heap size
-Xmx - Maximum Heap size

Depending on how your application runs, it CAN be beneficial to set them to the same value so that the JVM doesn't have to deallocate and reallocate memory on a regular basis.

For most of my microservices I create at work, I use:

java -server -Xms128m -Xmx128m -XX:+UseG1GC -jar /path/to/my/application.jar

That means that the Java Heap will NEVER use any more or less than 128MBytes of memory.. The JVM itself CAN use more than that for it's own operation, but it's going to be minimal.. And remember, bigger is almost always NOT better... The more memory you allocate to the Heap, the more overhead you place on the memory management system and that wastes a lot of CPU cycles and causes more and longer GC pauses.

I hope this information is helpful to those of you who use large Java applications regularly and might be frustrated by certain behaviors. Once an application is tuned properly, it often runs for years without issue. 

I look forward to any questions you might have, and hope that you all have a wonderful New Year!!!

Comments

Unknown said…
Using 64-bit Java implies both the -d64 and -server options for JAVA_OPTS although including those certainly wouldn't hurt but this definitely provides some great information, as well as an overview of changes in Java 8.

The article I found is located here
Deven Phillips said…
When running on Linux, those are default.

Popular Posts