DEV Community

Cover image for Understanding Java OutOfMemory Issues
Dapinder
Dapinder

Posted on • Updated on

Understanding Java OutOfMemory Issues

The article was written for Java 5 and originally published on Blogspot on April 24, 2013

One of the most common problems that beset enterprise applications is the dreaded OutOfMemoryError. The error is typically followed by one of the following:

  • Either you application server crashes.
  • Or you have severe performance bottleneck.
  • A stuck thread waiting on resources leading nowhere and ultimately crashing the server.

Regardless of the symptoms, you will most likely need to reboot the application server to make think ok.

Causes of out-of-memory errors

Before you attempt to resolve an out-of-memory error, first understanding how it can occur is beneficial. If the JVM runs out of memory anywhere in its process memory space, including all regions in the heap as well as the permanent memory space, and a process attempts to create a new object instance, the garbage collector executes to try to free enough memory to allow the new object's creation. If the garbage collector cannot free enough memory to hold the new object, then it throws an OutOfMemoryError.

Out-of-memory errors most commonly result from Java memory leaks. Java memory leak is the result of maintaining a lingering reference to an unused object: you are finished using an object, but because one or more other objects still reference that object, the garbage collector cannot reclaim its memory. The memory occupied by that object is thus lost from the usable heap.
An object is considered garbage when it can no longer be reached from any pointer in the running program. The most straightforward garbage collection algorithms simply iterate over every reachable object. Any objects left over are then considered garbage. The time this approach takes is proportional to the number of live objects, which is prohibitive for large applications maintaining lots of live data.

These types of memory leaks typically occur during Web requests, and while one or two leaked objects may not crash your application server, 10,000 or 20,000 requests might. Furthermore, most objects that are leaked are not simple objects such as Integers or Doubles, but rather represent subgraphs within the heap. For example, you may inadvertently hold on to a Person object, and that Person object has a Profile object that has several PerformanceReview objects that each maintain sets of data. Rather than losing 100 bytes of memory that the Person object occupies, you lose the entire subgraph that might account for 500 KB or more of memory.

JVM memory management

The Java HotSpot VM garbage collector uses generational GC. Generational GC takes advantage of the observation that most programs conform to the following generalisations.

  • They create many objects that have short lives, for example, iterators and local variables.
  • They create some objects that have very long lives, for example, high level persistent objects.

Garbage collection occurs in each generation when the generation fills up. Sun JVM is divided into generations and is into following:

  • Young generation, including Eden and two survivor spaces (the From space and the To space)
  • Old generation
  • Permanent generation (Non-Heap from JDK 1.6 onwards)
  • Code-Cache (Non-Heap from JDK 1.6 onwards)

Young generation, including Eden and two survivor spaces: The vast majority of objects are allocated in a pool dedicated to young objects (the young generation) (Objects are created in Eden), and most objects die there. .
When the young generation fills up it causes a Minor Collection in which only the young generation is collected; garbage in other generations is not reclaimed.
The young generation consists of Eden and two Survivor spaces. When Eden is full, the garbage collector iterates over all objects in Eden, copies live objects to the first survivor space, and frees memory for any dead objects. When Eden again becomes full, it repeats the process by copying live objects from Eden to the second survivor space, and then copying live objects from the first survivor space to the second survivor space.

Objects are copied between survivor spaces in this way until they are old enough to be tenured (copied to the tenured generation). Most objects become garbage short after creation (die young)

By separating persistent objects from short-lived objects and moving them to designated spaces, the garbage collector can free memory for use by the application (improving efficiency of memory use), and avoid examining every object each time a collection is done (reduce garbage collection overhead).

Java Heap Generations

Old/Tenured generation: Typically, each time during Minor Collection, some objects gets survived (second survivor space fills) and live objects remain in Eden or in the first survivor space, then these objects are tenured (that is, they are copied to the old generation) and are further moved to the tenured generation. Eventually, the tenured generation will fill up and must be collected Or When the garbage collector cannot reclaim enough memory by executing this type of minor collection, also known as a copy collection, then it performs a Major Collection, in which the entire heap is collected. Major collections usually last much longer than minor collections because a significantly larger number of objects are involved.

Non-heap memory includes a method area shared among all threads and memory required for the internal processing or optimization for the Java VM. It stores per-class structures such as a runtime constant pool, field and method data, and the code for methods and constructors. The method area is logically part of the heap but, depending on the implementation, a Java VM may not garbage collect or compact it. Like the heap memory, the method area may be of a fixed or variable size. The memory for the method area does not need to be contiguous. In addition to the method area, a Java VM may require memory for internal processing or optimization which also belongs to non-heap memory. For example, the Just-In-Time (JIT) compiler requires memory for storing the native machine code translated from the Java VM code for high performance.

Memory and Non-heap Memory

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. The heap itself only contains class instances, but before the JVM can create an instance of a class on the heap, it must load the class bytecode (.class file) into the process memory. It can then use that class bytecode to create an instance of the object in the heap. The space in the process memory that the JVM uses to store the bytecode versions of classes is the permanent space. In general, you want the permanent space to be large enough to hold all classes in your application, because reading classes from the file system is obviously more expensive than reading them from memory. To help you ensure that classes are not unloaded from the permanent space, the JVM has a tuning option:

–noclassgc

This option tells the JVM not to perform garbage collection on (and unload) the class files in the permanent space. This tuning option is very intelligent, but it raises a question: what does the JVM do if the permanent space is full when it needs to load a new class? In my observation, the JVM examines the permanent space and sees that it needs memory, so it triggers a major garbage collection. The garbage collection cleans up the heap, but cannot touch the permanent space, so its efforts are fruitless. The JVM then looks at the permanent space again, sees that it is full, and repeats the process again, and again, and again.

So the Java classes are stored in the permanent generation. What all does that entail? Besides the basic fields of a Java class there are:

  • Methods of a class (including the bytecodes)
  • Names of the classes (in the form of an object that points to a string also in the permanent generation)
  • Constant pool information (data read from the class file, see chapter 4 of the JVM specification for all the details).
  • Object arrays and type arrays associated with a class (e.g., an object array containing references to methods).
  • Internal objects created by the JVM (java/lang/Object or java/lang/exception for instance)
  • Information used for optimisation by the compilers (JITs)

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.

Available Garbage Collectors in Heap

The Java HotSpot VM includes three types of collectors and each with different performance characteristics. These collectors are responsible what type of collection of unreachable/dead objects will have in the VM. One can even force during the VM start-up which collector to use else the collector is selected by default based on machine's hardware and OS configurations. There are two primary measure of garbage collection performance i.e. applications and Garbage Collection can be classified as Throughput and Pauses.

Throughput is the percentage of total time not spent in garbage collection. Pauses are the times when an application becomes unresponsive because garbage collections is taking place.

Throughput bound applications are the ones which will be needing to perform as many transactions as possible. "Application Throughput" denotes the speed at which a Java application runs. If your application is a transaction based system, high throughput means that more transactions are executed during a given amount of time. You can also measure the throughput by measuring how long it takes to perform a specific task or calculation. For most of web applications, the right measurement is considered to be Throughput since Pauses during GC generally get unnoticed. But not for all, e.g., in an online picture editing too even short pauses may impact to user behaviour heavily.

Latency (Pauses) bound applications will be needing to complete each transaction in a specified amount of time. Low Pause collectors are needed for Latency bound applications. In latency (Low pause) applications, GC threads will be running alongside with the application and hence throughput will be low.

  1. Serial Collector
    The serial collector uses single thread to accomplish all garbage collection which means there is no overhead of thread communication or need to have synchronization.-XX:+UseSerialGC is the command line option.

  2. Parallel Collector (Throughput Collector)
    The parallel collector is similar to serial collector with the difference that multiple threads are used to spped-up the garbage collection time. By default, only Minor Collections happens with multiple threads; major collections are still performed with a single thread.
    Use -XX:+UseParallelGC. There is a feature introduced in Java 5 called "parallel compaction" through which even Major Collections can happen with multiple threads as well. Parallel compaction is enabled by adding the option -XX:+UseParallelOldGC to the command line. It is automatically chosen as the default garbage collector on server-class machines.

  3. Concurrent Collector (Low Pause)
    This collector is best suited for applications where objects are long lived i.e. larger Tenured Generation and run on machines with two or more processors. Also, this collector should be considered for any application with a low pause time requirement. -XX:+UseConcMarkSweepGC. If you want it to be run in incremental mode(low pause times) , also enable that mode via the –XX:+CMSIncrementalMode.

OutOfMemoryError in Java Heap

When JVM starts JVM heap space is equal to the initial size of Heap specified by -Xms parameter, as application progress more objects get created and heap space is expanded to accommodate new objects. JVM also run garbage collector periodically to reclaim memory back from dead objects. JVM expands Heap in Java some where near to Maximum Heap Size specified by -Xmx and if there is no more memory left for creating new object in java heap , JVM throws java.lang.OutOfMemoryError and your application dies.

Out of Permgen space

  1. Since in most of JVM default size of Perm Space is around "64MB" one can easily run out of memory if you have too many classes or huge number of Strings in your project.
  2. Important point to remember is that it doesn't depends on –Xmx value so no matter how big your total heap size you can run OutOfMemory in perm space. Good think is you can specify size of permanent generation using JVM options "-XX:PermSize" and "-XX:MaxPermSize" based on your project need. When sizing the permanent space, consider using 128 MB, unless your applications have a large number of classes, in which case, you can consider using 256 MB. If you have to configure the permanent space to use anything more, then you are only masking the symptoms of a significant architectural issue
  3. Another reason of "java.lang.OutOfMemoryError: PermGen" is memory leak through Classloaders. In Application server different classloaders are used to load different web application so that you can deploy and undeploy one application without affecting other application on same server, but while undeploying if container some how keeps reference of any class loaded by application class loader than that class and all other related class will not be garbage collected and can quickly fill the PermGen space if you deploy and undeploy your application many times. "java.lang.OutOfMemoryError: PermGen”.

Possible Solutions to try

  1. From tomcat > 6.0 onward tomcat provides memory leak detection feature which can detect many common memory leaks on web-app perspective e.g ThreadLocal memory leaks, JDBC driver registration, RMI targes, LogFactory and Thread spawned by web-apps. You can check complete details on http://wiki.apache.org/tomcat/MemoryLeakProtection you can also detect memory leak by accessing manager application which comes with tomcat, in case you are experiencing memory leak on any java web-app its good idea to run it on tomcat.
  2. Increasing the "-XX:MaxPermSize"
  3. We can keep a check on if the Strings in our applications are huge making String Constant Pool to grow at a larger extent.
  4. The most pain-free method of resolving this issue is to switch from Sun's JDK implementation to Oracle's freely available JRockit implementation as JRockit JVM DOES NOT have any PermGen Space inside the JVM memory. (But this should be the LAST solution)

Out of swap space

Java HotSpot VM is split between 3 memory spaces (Java Heap, PermGen, C-Heap). failure to allocate native memory from the OS to the HotSpot C-Heap or dynamically expand the Java Heap

Possible Solutions to try

Possible causes are non-optimal heap space allocation - can be fixed by adjusting the correct heap size and/or increasing RAM

GC Overhead limit exceeded

Pre-cursor for java heapspace - warning that garbage collection is taking too much time, that can possibly lead to heap space error - before jvm crash

Possible Solutions to try

Can turn this off - the root cause should be fixed for java heap space to avoid this one.

The maximum theoretical heap limit for the 32-bit JVM is 4GB. Due to various additional constraints such as available swap, kernel address space usage, memory fragmentation, and VM overhead, in practice the limit can be much lower. On most modern 32-bit Windows systems the maximum heap size will range from 1.4G to 1.6G. On 32-bit Solaris kernels the address space is limited to 2G. On 64-bit operating systems running the 32-bit VM, the max heap size can be higher, approaching 4G on many Solaris systems.As of Java SE 6, the Windows /3GB boot.ini feature is not supported.If your application requires a very large heap you should use a 64-bit VM on a version of the operating system that supports 64-bit applications. See Java SE Supported System Configurations for details.

Top comments (0)