Java'€™s garbage-collection feature provides significant benefits over non-garbage-collected languages. The garbage collector (GC) is designed to automatically reclaim unreachable memory and to avoid memory leaks. Although the GC is quite adept at performing this task, a malicious attacker can nevertheless launch a denial-of-service (DoS) attack against the GC, such as by inducing abnormal heap memory allocation or abnormally prolonged object retention. For example, some versions of the GC could need to halt all executing threads to keep up with incoming allocation requests that trigger increased heap management activity. System throughput rapidly diminishes in this scenario.

Real-time systems, in particular, are vulnerable to a more subtle slow-heap-exhaustion DoS attack, perpetrated by stealing CPU cycles. An attacker can perform memory allocations in a way that increases the consumption of resources (such as CPU, battery power, and memory) without triggering an OutOfMemoryError. Writing garbage-collection–friendly code helps restrict many attack avenues.

Use Short-Lived Immutable Objects

Beginning with JDK 1.2, the generational GC has reduced memory allocation costs, in many cases to levels lower than in C or C++. Generational garbage collection reduces garbage-collection costs by grouping objects into generations. The younger generation consists of short-lived objects. The GC performs a minor collection on the younger generation when it fills up with dead objects [Oracle 2010a]. Improved garbage-collection algorithms have reduced the cost of garbage collection so that it is proportional to the number of live objects in the younger generation rather than to the number of objects allocated since the last garbage collection.

Note that objects in the younger generation that persist for longer durations are tenured and are moved to the tenured generation. Few younger-generation objects continue to live through to the next garbage-collection cycle. The rest become ready to be collected in the impending collection cycle [Oracle 2010a].

With generational GCs, use of short-lived immutable objects is generally more efficient than use of long-lived mutable objects, such as object pools. Avoiding object pools improves the GC's efficiency. Object pools bring additional costs and risks: they can create synchronization problems and can require explicit management of deallocations, possibly creating problems with dangling pointers. Further, determining the correct amount of memory to reserve for an object pool can be difficult, especially for mission-critical code. Use of long-lived mutable objects remains appropriate when allocation of objects is particularly expensive (for example, when performing multiple joins across databases). Similarly, object pools are an appropriate design choice when the objects represent scarce resources, such as thread pools and database connections.

Avoid Large Objects

The allocation of large objects is expensive, in part because the cost to initialize their fields is proportional to their size. Additionally, frequent allocation of large objects of different sizes can cause fragmentation issues or compacting collect operations.

Do Not Explicitly Invoke the Garbage Collector

The GC can be explicitly invoked by calling the System.gc() method. Even though the documentation says that it "runs the garbage collector,"€ there is no guarantee as to when or whether the GC will actually run. In fact, the call merely suggests that the GC should subsequently execute; the JVM is free to ignore this suggestion.

Irresponsible use of this feature can severely degrade system performance by triggering garbage collection at inopportune moments rather than waiting until ripe periods when it is safe to garbage-collect without significant interruption of the program'€™s execution.

In the Java Hotspot VM (default since JDK 1.2), System.gc() forces an explicit garbage collection. Such calls can be buried deep within libraries, so they may be difficult to trace. To ignore the call in such cases, use the flag -XX:+DisableExplicitGC. To avoid long pauses while performing a full garbage collection, a less demanding concurrent cycle may be invoked by specifying the flag -XX:ExplicitGCInvokedConcurrent.

Applicability

Misusing garbage-collection utilities can cause severe performance degradation resulting in a DoS attack.

When an application goes through several phases, such as an initialization and a ready phase, it could require heap compaction between phases. The System.gc() method may be invoked in such cases, provided a suitable uneventful period occurs between phases.

Related Vulnerabilities

The Apache Geronimo and Tomcat vulnerability GERONIMO-4574, reported in March 2009, resulted from PolicyContext handler data objects being set in a thread and never released, causing these data objects to remain in memory longer than necessary.

Bibliography

 


9 Comments

  1. This guideline appears to be a collection of 6 specific smaller guidelines. Each of these needs to stand on it's own and should most likely be broken out into separate guidelines, possibly in a Garbage Collection section of the standard. This means that each of these "h1" headers need their own CS/NCE pairs.

  2. The following NCE appears under "Nulling References"

    int[] buffer = new int[100];
    doSomething(buffer);
    buffer = null  // No need to explicitly assign null
    

    I have a hard time seeing this code as nonconforming and requiring diagnostics. The intro says "It adds clutter to the code and can introduce subtle bugs." We will need an example of such a bug to keep this.

  3. The Compliant Solution (Set reference to null) is contraindicated by the previous advice: Do not attempt to "help the garbage collector" by setting local reference variables to null.

    Also the Compliant Solution (Use Null Object pattern) should reference OBJ12-J (or now EXC15-J) which provides more info.

  4. The risk assessment seems to only narrowly apply to one specific example. Can this explanation be expanded to encompasses the entire guideline?

  5. This sentence is an awful recommendation for a variety of reasons:

    "System.gc() may be invoked as a last resort in a catch block that is attempting to recover from an OutOfMemoryError."

    The two primary problems that jump to mind first are:

    1. you pretty much cannot recover from an OOME.  99% of the time your application will end up in a crippled state (since you can't predict where the error will be thrown how it will impact your application) and the best you can do is try to exit gracefully.
    2. the garbage collector itself has already thrashed around for a while trying to free up memory.  it finally gave up and threw an OOME.  why would telling it to try again have any meaningful affect?

     

    1. I took that sentence out.

  6. In the "Avoid Large Objects" section, we write (in part): "Additionally, frequent allocation of large objects of different sizes can cause fragmentation issues or noncompacting collect operations."

    Shouldn't that be "...or compacting collect operations."???  I thought that the NONcompacting collects are the cheap ones, because they need not move objects around in memory.

    1. wrt compacting collect operations: I made this change.

    • The references from Oracle seem to be dated 2010 and we need to check what the modern GC does and whether the text does justice to it
    • I was hoping to see some code in this guideline
      • We could explain when an object gets actually eligible for GC using an example