Programming errors can prevent garbage collection of objects that are no longer relevant to program operation. The garbage collector collects only unreachable objects; consequently, the presence of reachable objects that remain unused indicates memory mismanagement. Consumption of all available heap space can cause an OutOfMemoryError
, which usually results in program failure. Memory leaks afford adversaries with a potentially exploitable denial of service attack, and consequently are forbidden.
Noncompliant Code Example (Off-By-One Programming Error)
This noncompliant code example shows a leaking Vector
object. The condition for removing the vector
element is mistakenly written as n > 0
instead of n >= 0
. Consequently, the method leaks one vector
per invocation, and quickly exhausts the available heap space.
public class Leak { static Vector vector = new Vector(); public void useVector(int count) { for (int n = 0; n < count; n++) { vector.add(Integer.toString(n)); } // ... for (int n = count - 1; n > 0; n--) { // Free the memory vector.removeElementAt(n); } } public static void main(String[] args) throws IOException { Leak le = new Leak(); int i = 1; while(true) { System.out.println("Iteration: " + i); le.useVector(1); i++; } } }
Compliant Solution (1)
This compliant solution corrects the mistake by changing the loop condition to n >= 0
.
public void useVector(int count) { for (int n = 0; n < count; n++) { vector.add(Integer.toString(n)); } // ... for (int n = count - 1; n >= 0; n--) { vector.removeElementAt(n); } }
Compliant Solution (2)
Prefer the use of standard language semantics where possible, as shown in this compliant solution.
public void useVector(int count) { for (int n = 0; n < count; n++) { vector.add(Integer.toString(n)); } // ... while (!vector.isEmpty()){ vector.removeElementAt(vector.size() - 1); } }
Compliant Solution (3)
An alternative way of clearing the vector
is to use the vector.clear()
method.
public void useVector(int count) { for (int n = 0; n < count; n++) { vector.add(Integer.toString(n)); } // ... vector.clear(); // Clear the vector }
Use the vector.subList(fromIndex, toIndex).clear()
method to remove a subrange of elements from the vector
. Note that the fromIndex
and the toIndex
can both be 0
as the count
variable is 1
on each iteration [[API 2006]].
Noncompliant Code Example (Non-Local Instance Field)
This noncompliant code example declares and allocates a HashMap
instance field that is used only in the doSomething()
method.
public class Storer { private HashMap<Integer,String> hm = new HashMap<Integer, String>(); private void doSomething() { hm.put(1, "java"); // hm is used only here and never referenced again // ... } }
Programmers may be surprised that the HashMap
will persist for the entire lifetime of the Storer
instance.
Compliant Solution (Reduce Scope of Instance Field)
This compliant solution declares the HashMap
as a local variable within the doSomething()
method.
public class Storer { private void doSomething() { HashMap<Integer,String> hm = new HashMap<Integer,String>(); hm.put(1,"java"); // ... } }
Localizing or confining the instance field to a narrower scope simplifies garbage collection; today's generational garbage collectors perform well with short-lived objects.
Noncompliant Code Example (Lapsed Listener)
This noncompliant code example, known as the Lapsed Listener, demonstrates unintentional object retention. The button
continues to hold a reference of the reader
object after completion of the readSomething()
method, even though the reader
object will never be used again.
public class LapseEvent extends JApplet { JButton button; public void init() { button = new JButton("Click Me"); getContentPane().add(button, BorderLayout.CENTER); Reader reader = new Reader(); button.addActionListener(reader); try { reader.readSomething(); } catch (IOException e) { // Handle exception } } } class Reader implements ActionListener{ public void actionPerformed(ActionEvent e) { Toolkit.getDefaultToolkit().beep(); } public void readSomething() throws IOException { // Read from file } }
Consequently, the garbage collector cannot collect the reader
object. A similar problem occurs with inner classes, because they hold an implicit reference to the enclosing class.
Noncompliant Code Example (Exception Before Remove)
Bloch 08 says: The best way to ensure that callbacks are garbage collected promptly is to store only weak references to them, for instance, by storing them only as keys in a WeakHashMap.
This noncompliant code example attempts to remove the reader through use of the removeActionListener()
method.
Reader reader = new Reader(); button.addActionListener(reader); try { reader.readSomething(); // Can skip next line button.removeActionListener(reader); // Dereferenced, but control flow can change } catch (IOException e) { // Forward to handler }
However, the removeActionListener
statement will never executed in the event of an exception thrown by the reader.readSomething()
method.
Compliant Solution (finally
Block)
This compliant solution uses a finally
block to ensure that the reader
object's reference is unregistered.
Reader reader = new Reader(); button.addActionListener(reader); try { reader.readSomething(); } catch (IOException e) { // Handle exception } finally { button.removeActionListener(reader); // Always executed }
Noncompliant Code Example (Member Object Leaks)
This noncompliant code example implements a stack
data structure [[Bloch 2008]] that continues to hold references to elements after thay have been the pop
-ed from the stack.
public class Stack { private Object[] elements; private int size = 0; public Stack(int initialCapacity) { this.elements = new Object[initialCapacity]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { // This method causes memory leaks if (size == 0) { throw new EmptyStackException(); } return elements[--size]; } /** * Ensure space for at least one more element, roughly * doubling the capacity each time the array needs to grow. */ private void ensureCapacity() { if (elements.length == size) { Object[] oldElements = elements; elements = new Object[2 * elements.length + 1]; System.arraycopy(oldElements, 0, elements, 0, size); } } }
The object references are retained even after the element is pop'ed. Such obsolete references remain live, and consequently cannot be garbage collected.
Compliant Solution (Assign null
to Elements of Data Structures)
This compliant solution assigns null
values to all obsolete references.
public Object pop() { if (size==0) throw new EmptyStackException(); // Ensures object consistency Object result = elements[--size]; elements[size] = null; // Eliminate obsolete reference return result; }
The garbage collector can include individual element-objects in its list of objects to free. A NullPointerException
results on subsequent attempts to access the particular object.
Although these examples appear trivial, and may fail to model production scenarios, obsolete references remain common when dealing with data structures such as hash tables containing many large-sized records. It is prudent to assign null
to array-like custom data structures; doing so with individual objects or local variables is unnecessary, because the garbage collector handles these cases automatically [[Commes 2007]].
Noncompliant Code Example (Strong References)
A common variant of obsolete objects is unintentional retention of objects in Collections
such as {{Map}}s. In this noncompliant code example, a server maintains temporary metadata about all committed secure connections.
class HashMetaData { private Map<SSLSocket, InetAddress> m = Collections.synchronizedMap(new HashMap<SSLSocket, InetAddress>()); public void storeTempConnection(SSLSocket sock, InetAddress ip) { m.put(sock, ip); } public void removeTempConnection(SSLSocket sock) { m.remove(sock); } }
Although the metadata is transient
by nature, it persists after the relevant socket is closed, until removeTempConnection()
is invoked. In the absence of notification logic, it is impossible to determine when to call removeTempConnection()
. Moreover, nulling out original objects or referents (Socket connections) is unwieldy.
Compliant Solution (Weak References)
This compliant solution uses weak references to allow timely garbage collection.
// ... private Map<SSLSocket, InetAddress> m = Collections.synchronizedMap(new WeakHashMap<SSLSocket, InetAddress>());
Strong references prevent the garbage collector from reclaiming objects that are stored compositely, such as in a Map
. According to the Java API [[API 2006]], weak reference objects: "... do not prevent their referents from being made finalizable, finalized, and then reclaimed." A referent is the object that is being referred to.
Keys held in WeakHashMap
objects are referenced through weak references. Objects become eligible for garbage collection when they lack strong references. Consequently, use of weak references allows the code to refer to the referent without delaying garbage collection of the referent. This approach is suitable only when the lifetime of the object is required to be the same as the lifetime of the key.
Simply facilitating garbage collection of unneeded objects through use of weak references is insufficient. Programs must also prune the data structure so that additional live entries can be accommodated. One pruning technique is to call the get()
method of WeakHashMap
and removing any entry that corresponds to a null
return value (polling). Use of reference queues is a more efficient method [[Goetz 2005b]].
Compliant Solution (Reference Queue)
Reference queues provide a way to receive notifications when a referent is garbage collected. When the referent is eventually garbage collected, the HashMap
continues to strongly reference both the WeakReference
object and the corresponding value (for each entry in the HashMap
).
When the GC clears the reference that referred to the referent, it adds the corresponding WeakReference
object to the reference queue. The WeakReference
object remains in the reference queue until some operation is performed on the queue (such as a put()
or remove()
). After such an operation, the WeakReference
object in the hashmap is also garbage collected. Alternatively, this two-step procedure can be carried out manually by using the following code:
class HashMetaData { private Map<WeakReference<SSLSocket>, InetAddress> m = Collections.synchronizedMap(new HashMap<WeakReference<SSLSocket>, InetAddress>()); ReferenceQueue queue = new ReferenceQueue(); public void storeTempConnection(SSLSocket sock, InetAddress ip) { WeakReference<SSLSocket> wr = new WeakReference<SSLSocket>(sock, queue); while ((wr = (WeakReference) queue.poll()) != null) { // poll for dead entries before adding more m.remove(wr); // Removes the WeakReference object and the value (not the referent) } m.put(wr, ip); } public void removeTempConnection(SSLSocket sock) { m.remove(sock); } }
Note that the two-argument constructor of WeakReference
takes a Queue
argument and must be used to perform direct queue processing. Dead entries should be pruned prior to insertion.
Compliant Solution (Soft References)
Use of soft references is also permitted. Soft references they guarantee that the referent will be reclaimed before an OutOfMemoryError
results, and also that the referent will remain live until memory begins to run out.
class HashMetaData { private Map<SoftReference<SSLSocket>, InetAddress> m = Collections.synchronizedMap(new HashMap<SoftReference<SSLSocket>, InetAddress>()); ReferenceQueue queue = new ReferenceQueue(); public void storeTempConnection(SSLSocket sock, InetAddress ip) { SoftReference<SSLSocket> sr = new SoftReference<SSLSocket>(sock, queue); while ((sr = (SoftReference) queue.poll()) != null) { m.remove(sr); // Removes the WeakReference object and the value (not the referent) } m.put(sr, ip); } public void removeTempConnection(SSLSocket sock) { m.remove(sock); } }
Soft references are preferred over weak references for applications such as caching because weak references are garbage collected more aggressively, to the point that they become unsuitable.
Risk Assessment
Memory leaks in Java applications may be exploited to cause denial of service.
Guideline |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
MSC06-J |
low |
unlikely |
high |
P1 |
L3 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this guideline on the CERT website.
Bibliography
[[API 2006]] Class Vector, Class WeakReference
[[Bloch 2008]] Item 6: Eliminate obsolete object references
[[Commes 2007]] Memory Leak Avoidance
[[Goetz 2005]] Lapsed listeners
[[Goetz 2005b]] "Memory leaks with global Maps" and "Reference queues"
[[Gupta 2005]]
[[MITRE 2009]] CWE ID 401 "Failure to Release Memory Before Removing Last Reference (aka 'Memory Leak')"
[!The CERT Oracle Secure Coding Standard for Java^button_arrow_left.png!] [!The CERT Oracle Secure Coding Standard for Java^button_arrow_up.png!] [!The CERT Oracle Secure Coding Standard for Java^button_arrow_right.png!]