From a security point of view, Java's garbage collection feature provides significant benefits over traditional languages such as C and C++. The garbage collector (GC) is designed to automatically reclaim unreachable memory and as a result avoid memory leaks. Although it is quite adept at performing this task, a malicious attacker can nevertheless launch a Denial of Service (DoS) attack, for example by inducing abnormal heap memory allocation or well as abnormally prolonged object retention.
For example, some versions of the GC may need to halt all executing threads in order 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 resource consumption (such as CPU, battery power, memory) without triggering an OutOfMemoryError.
Writing garbage collection friendly code helps restrict many attack avenues. Many of the best practices are enumerated below.
Since JDK 1.2, the generational garbage collector has reduced memory allocation related costs to low levels, in many cases lower than C/C++. 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 _total_ number of objects allocated since the last garbage collection. 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|AA. Bibliography#Oracle 10a]\]. |
Note that objects in the _younger generation_ that persist for longer durations are _tenured_ and are moved to the _tenured generation_. Very 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|AA. Bibliography#Oracle 10a]\]. |
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 garbage collector's efficiency. Object pools bring additional costs and risks: they can create synchronization problems, and can require explicit management of deallocations, which risks problems with dangling pointers. Further, determining the correct amount of memory to provision for an object pool can be difficult; this is especially problematic for mission critical code. Use of long-lived mutable objects remains appropriate in cases where allocation of objects is particularly expensive, such as 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.
This noncompliant code example (based on \[[Goetz 2004|AA. Bibliography#Goetz 04]\]) shows a container, {{MutableHolder}}. In {{MutableHolder}}, the instance field {{value}} can be updated to reference a new value using the {{setValue()}} method which makes its existence long-term. This slows down garbage collection. |
public class MutableHolder {
private Hashtable<Integer, String> value; // not final
public Object getValue() {
return value;
}
public void setValue(Hashtable<Integer, String> ht) {
value = (Hashtable<Integer, String>)ht;
}
}
|
This example also violates guideline FIO00-J. Defensively copy mutable inputs and mutable internal components.
This compliant solution uses a custom container called ImmutableHolder. To aid garbage collection, it is recommended that short-lived ImmutableHolder objects be created by passing Hashtable instances to the constructor.
public class ImmutableHolder {
private final Hashtable<Integer, String> value;
// create defensive copy of inputs
public ImmutableHolder(Hashtable<Integer, String> ht) {
value = (Hashtable<Integer, String>)ht.clone();
}
// create defensive copy while returning
public Object getValue() {
return value.clone();
}
}
|
When value is assigned in ImmutableHolder's constructor during object creation, it is a younger member field (of type ImmutableHolder.Hashtable<Integer, String>) that is referencing an older object in the client code (of the same type Hashtable<Integer, String>). This is a much better position to be in as far as the garbage collector is concerned. Note that a shallow copy is used in this case to preserve references to the older value.
The allocation of large objects is expensive; further, 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 non compacting collect.
The new IO classes (NIO) in java.nio allow the creation and use of direct buffers. These buffers tremendously increase throughput for repeated IO activities. However, their creation and reclamation is more expensive than that for heap-based non-direct buffers, because direct buffers are managed using OS specific native code. This added management cost makes direct buffers an poor choice for single-use or infrequently used cases. Direct buffers are also not subject to Java's garbage collector which can cause memory leaks. Frequent allocation of large direct buffers can cause an OutOfMemoryError.
This noncompliant code example uses a short-lived local object buffer, which is allocated in non-heap memory and is not garbage collected.
ByteBuffer buffer = ByteBuffer.allocateDirect(8192); // use buffer once |
This compliant solution uses an indirect buffer to allocate the short-lived, infrequently used object.
ByteBuffer buffer = ByteBuffer.allocate(8192); // use buffer once |
Reference nulling to "help the garbage collector" is unnecessary. It adds clutter to the code and can introduce subtle bugs. Assigning null to local variables is also unnecessary; the Java Just-In-Time compiler (JIT) can perform an equivalent liveness analysis — most implementations do this. A related bad practice is use of a finalizer to null out references; see OBJ08-J. Avoid using finalizers for additional details.
This noncompliant code example assigns null to the buffer array.
int[] buffer = new int[100]; doSomething(buffer); buffer = null // No need to explicitly assign null |
This compliant solution adds a lexical block to limit the scope of the variable {{buffer}}; the garbage collector can collect the object immediately when it goes out of scope \[[Bloch 2008|AA. Bibliography#Bloch 08]\]. |
{ // limit the scope of buffer
int[] buffer = new int[100];
doSomething(buffer);
}
|
Array based data structures such as ArrayLists are exceptions because the programmer may be required to explicitly set individual array elements to null to indicate their absence.
Always remove short-lived objects from long-lived container objects when the task is over. For example, objects attached to a java.nio.channels.SelectionKey object must be removed when they are no longer needed. Doing so reduces the possibility of memory leaks.
The garbage collector 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 on when the garbage collector will actually run; in fact, the call only suggests that the GC will subsequently execute. Other reasons to avoid explicit invocation of the GC include:
System.gc().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 and difficult to trace. To ignore the call in such cases, use the flag -XX:+DisableExplicitGC. To avoid long pauses while doing a full GC, a less demanding concurrent cycle can be invoked by specifying the flag -XX:ExplicitGCInvokedConcurrent.
There are some exceptions to this recommendation. When an application goes through several phases such as an initialization and a ready phase, it may require heap compaction between phases. Given an uneventful period, System.gc() may be invoked in such cases, provided that there is a suitable uneventful period between phases. System.gc() may also be invoked as a last resort in a catch block that is attempting to recover from an OutOfMemoryError.
Misusing some garbage collection utilities can cause Denial Of Service (DoS) related issues and severe performance degradation.
Guideline |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
|---|---|---|---|---|---|
OBJ13-J |
low |
likely |
high |
P3 |
L3 |
\[[API 2006|AA. Bibliography#API 06]\] Class {{System}}
\[[Bloch 2008|AA. Bibliography#Bloch 08]\] Item 6: "Eliminate obsolete object references"
\[[Commes 2007|AA. Bibliography#Commes 07]\] Garbage Collection Concepts and Programming Tips
\[[Goetz 2004|AA. Bibliography#Goetz 04]\]
\[[Lo 2005|AA. Bibliography#Lo 05]\]
\[[MITRE 2009|AA. Bibliography#MITRE 09]\] [CWE ID 405|http://cwe.mitre.org/data/definitions/405.html] "Asymmetric Resource Consumption (Amplification)" |
OBJ12-J. Use checked collections against external code 08. Object Orientation (OBJ) OBJ14-J. Encapsulate the absence of an object by using a Null Object