Analysing and fixing Java Heap related Issues
Java Heap related issues can cause severe damage to our application and we might see poor user experience. Java Heap is memory used by your application which is used to create and store objects. We can define the maximum memory for heap by specifying -Xmx<size> by setting the java variable. When your application runs, it will gradually fill up Heap and memory must be released to keep our application working. Hence Java periodically runs a process called ‘Garbage collection(GC)‘ which will scan the heap and clear objects that are no longer referenced by your application.One cannot request Garbage Collection on demand. Only JVM can decide when to run GC.
A Heap dump is a snapshot of the memory of when the heap dump was taken.Heap dumps are not commonly used because they are difficult to interpret. But, they have a lot of useful information in them if you know where/how to look at them. The most common usage is to locate memory leaks.
Collecting and Analysing the Heap dump and GC logs
1. You can use commands -verbose:gc to get verbose GC logs and can analyse the gc logs using the tools e.g. fasterj , gceasy etc.
2. You can use JDK tools such as jconsole,jstat, jmap, VisualVM etc.
3. You can use the tool like Eclipse Memory Analyzer.
4. You can use APM (Application Performance Management) tool e.g. New Relic, Dynatrace, AppDynamics etc.
Few common errors and their possible fixes are below,
‘OutOfMemoryError’ due to insufficient Heap
This error simply means that the Heap is filled with objects that are being used/referenced by your application and GC is not able to release memory. There can be real demand of memory and you do not have sufficient memory hence we need to fix this by increasing the memory size of heap.
You can identify this by looking at the heap uses graph. You might see sudden increase (spike) in memory utilisation.
Error ‘OutOfMemoryError’ due to Memory Leak
Memory leak means the application is allocating memory and holding on to them unnecessarily.
When you look at the heap graph, you would see a ‘stair case’ pattern i.e. a gradual leak (rather than a sudden spike). You will see errors ‘OutOfMemoryError‘.
Memory leak is most probably code issue. You can use the java profiling tools e.g. Jprobe, TPTP etc to identify this.
Heap gets fragmented when small and large objects are allocated in a mixed fashion. To some extent, we cannot avoid fragmentation and over time, heap will get fragmented. But we can consider below points to avoid this,
1. When heap is fragmented, GC will try to compact the heap which can result in a longer GC pause time for a heavily fragmented heap
2. Heap fragmentation becomes an issue when your application requires to allocate memory for a large object (which will need contiguous blocks of memory) and we have memory available but in small blocks.
We might see poor performance and out of memory errors when heap fregmentation happens. Tuning of JVM can help here, also we feel this should not be issue with new JVMs.
Garbage Collection Pauses
When GC takes a long time to complete the JVM pause time also becomes long resulting in very poor end user experience. Ideally each GC pause should be less than 500 ms depending upon how often the GC runs.
When we look at the graph for GC Time, we will see longer duration (several seconds).
Tuning can help here. We can have ‘generational‘ heap configured. With generational heap, the heap has a special area called ‘Nursery‘ or ‘New Generation’ that is used for short lived objects. The idea is GC is will be quicker in ‘New Generation’ since the entire heap does not have to be scanned.
This error message indicates that the permanent generation is full. This is a separate space for class definitions and related data. If an application loads a large number of classes, then the size of the permanent generation might need to be increased using the -XX:MaxPermSize option.
Interned java.lang.String objects are also stored in the permanent generation. The java.lang.String class maintains a pool of strings. When the intern method is invoked, the method checks the pool to see if an equivalent string is present. If so, it’s returned by the intern method and if not then string is added to the pool. If an application interns a large number of strings, you might need to increase the size of the permanent generation.
We can use the jmap -permgen command to print statistics related to the permanent generation, including information about internalised String instances.
The memory pools available depend on which version of the Java VM is being used. For the HotSpot Java VM, the memory pools for serial garbage collection are the following.
- Eden Space (heap): The pool from which memory is initially allocated for most objects.
- Survivor Space (heap): The pool containing objects that have survived the garbage collection of the Eden space.
- Tenured Generation (heap): The pool containing objects that have existed for some time in the survivor space.
- Permanent Generation (non-heap): The pool containing all the reflective data of the virtual machine itself, such as class and method objects. With Java VMs that use class data sharing, this generation is divided into read-only and read-write areas.
- Code Cache (non-heap): The HotSpot Java VM also includes a code cache, containing memory that is used for compilation and storage of native code.