A couple of days ago I ran into a situation that reminded me of those old times. Luckily, the years gone by have accumulated some insight about JVM internals in me, and I thought I’d share my current thoughts in a form of a blog post.
To start off, some of you might remember Java 1.1 that we used back in late nineties. Back then you actually had the possibility to turn off the GC. At least in Solaris, where the Sun-provided version of the JVM offered a possibility to add a -noasyncgc option to your JVM startup parameters. The option was still supported until the release of JDK 1.4 for backward compatibility, but already starting from JDK 1.2 it didn’t do anything. Beside adding complexity to your startup scripts.
The option turned off the JVM-controlled garbage collector. You could still collect the unused objects by explicitly invoking System.gc() from your code. Sounds like the level of flexibility an experienced engineer could put into good use. So – why was this option removed?
In fact, the motivations behind this move start to make sense, when you consider the following:
- By disabling the GC you essentially claim that you know how much memory your application would require during runtime. But what if you are wrong? Once the heap fills up, having the GC disabled would cause the death of your application.
- Invoking System.gc() might not execute the garbage collection at all. In modern JVM’s it is nothing more but a recommendation to the JVM that “I think it is a good spot to run the GC”. And – your sysadmin might have disabled the System.gc() calls at all by specifying -XX:+DisableExplicitGC startup parameter.
- If System.gc() is actually executed by the JVM, it results in a full garbage collection. This tends to be very expensive with large heaps and result in long pause times.
- You will still not achieve predictable GC timing by invoking a System.gc(), especially in multithreaded applications.
Now, look at the points above and imagine an application that would run in a JVM without automatic GC. You would probably not want to bet your house on its behavior. The hair on my back immediately start rising when I try to picture a debugging session to trace down any performance issues with that application. So maybe mr. Gosling wasn’t making a design error after all.
But what if my stop-the-world pauses are intolerably long? And I really-really wish to turn the GC off? There actually are some possibilities:
- There is a part of memory allocated by the JVM in which you can turn off the GC. If you wish you can prohibit GC on your class definitions by specifying -Xnoclassgc in your JVM options.
- Depending on your application, you might get rid of full GC pauses in compaction phases by tweaking your young and tenured generation sizes. In those cases you can configure the JVM to run only concurrent GC cycles, which in most cases do not affect the performance.
- You can allocate memory outside the heap. Those allocations obviously aren’t visible to the garbage collector and thus will not be collected. It might sound scary, but already from Java 1.4 we have access to the java.nio.ByteBuffer class which provides us a method allocateDirect() for off-heap memory allocations. This allows us to create large data structures, which is especially useful when running on a 32-bit architecture. This solution is not too uncommon – many BigMemory implementations are using ByteBuffers under the hood. Terracotta BigMemory and Apache DirectMemory for example.
However, I would advise you to turn to those solutions only if you are sure about what you are doing. In 99.9% of the cases the garbage collector will be smarter than you.