This article on Netbeans.org discusses using the profiler bundled with the Netbeans IDE to detect memory leaks. By monitoring the surviving generations metric, one can supposedly catch a leak. I believe that this article ignores a certain situation.

Before we get into the details, let’s define some key terms (these definitions are from the above mentioned article). First, a generation is a set of instances of a particular class that are all created within the same interval between garbage collections. Next, a surviving generation is a generation that has survived at least one garbage collection and is identified by the number of garbage collections survived. And finally, the surviving generations metric from the Netbeans profiler represents the total number of surviving generations that currently exist on the heap.

The article states that the number of surviving generations will grow “if there is a memory leak in an application which prevents newly-created objects from being release from the heap”. It continues with the idea that any application will eventually reach a stable number of surviving generations. Those applications that do not attain a consistent number of surviving generations must suffer from a memory leak.

Is there a case where an application will have a steadily increasing number of surviving generations, never reach a stable number, and still not have a leak? Consider the following case, and decide for yourself.

Suppose that our application runs with a total memory footprint much smaller than the allocated JVM heap space, and the JVM has an explicitly specified throughput goal. This application allocates vast numbers of relatively heavyweight objects (we’ll call them type-A objects) that are not deallocated by minor garbage collection cycles. Some period of time after being created but before the program terminates, these type-A objects no longer have any references to them, so they become candidates for garbage collection.

During this processing, though, new type-A objects are created at a rate faster than the garbage collector reclaims the old objects, because the old objects no longer belong to the youngest generation, and there are a significant number of new objects that do belong to the young generation that can be collected. In this situation, the garbage collector can perform quick collections on only the youngest generation and then resume normal program execution.

In this way, our slightly heavyweight type-A objects are not garbage collected, because the garbage collector is able to achieve the desired throughput goal by only doing minor collections, and our type-A objects no longer belong to the youngest generation.

In this situation, the total number of surviving generations for type-A objects will continue to increase throughout the program’s execution. Even so, there is no memory leak, since all type-A objects that are no longer used are candidates for garbage collection. It just so happens that, as a result of the total memory footprint of the application combined with the JVM throughput goal, only minor garbage collections occur. Because our objects do not belong to the youngest generation when they become candidates for deallocation, they are never reclaimed, and the number of surviving generations grows throughout the application’s lifetime.

This situation, although it may not be the most likely case, illustrates that a steadily increasing number of surviving generations does not always mean an application has a memory leak. While it is true that this metric can help identify memory leaks, it cannot be treated as a guaranteed indication of a leak. The intelligent way to interpret this metric is to consider it as it applies to each specific situation, as is often the case, and not rely on a broad generalization to profile your applications.

Leave a Reply