You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 22 Next »

Even though Java supports memory management through garbage collection, there are innumerable possibilities of introducing memory leaks due to programming errors. Furthermore, the garbage collector collects only the unreachable objects and not those that are still reachable. The presence of reachable objects that remain unused clearly call for memory mismanagement.

Depending on program scale and available memory, one of the most undesired errors, the OutOfMemoryError may manifest itself wherein the heap space runs out causing program failure.

Noncompliant Code Example

This noncompliant example shows a leaking vector object. This quickly exhausts the heap space as the programmer has mistakenly written the condition for removing the vector element as n>0 instead of n>=0. As a result, in every iteration the method leaks one vector element.

public class Leak {
  static Vector vector = new Vector();
  public void leakingVector(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.leakingVector(1);
      i++;
    }
  }
}

Compliant Solution (1)

The compliant solution corrects the mistake by changing the loop condition to n>=0.

for (int n=count-1; n>=0; n--) {
  vector.removeElementAt(n);
}	

Compliant Solution (2)

To be safe, it is usually better to use the standard language semantics as shown below.

while (!vector.isEmpty()){
  vector.removeElementAt(vector.size() - 1);	        	    
}

Compliant Solution (3)

An alternative (and preferable) way to clear the vector is to use vector.clear(). Likewise, if a range of elements has to be released from a vector, vector.subList(fromIndex, toIndex).clear() can be used. In this case the fromIndex and the toIndex would both be 0 as the count variable is 1 on each iteration. [[API 06]]

vector.clear();

Noncompliant Code Example

This noncompliant example creates a HashMap instance field within the class body but uses it only in the doSomething method. Sometimes, it is not obvious that it will continue to persist as long as class BadScope's instance is alive.

public class BadScope {
  private HashMap<Integer,String> hm = new HashMap<Integer,String>();
  
  private void doSomething() {
    hm.put(1,"java");  // hm is used only here
  }
}

Compliant Solution

Localizing or confining the instance field to a narrower scope gives the garbage collector a better chance of succeeding at collecting the object in a timely manner. Short-lived objects are always collected quickly by generational garbage collectors.

public class GoodScope {
  private void doSomething() {
    HashMap<Integer,String> hm = new HashMap<Integer,String>();
    hm.put(1,"java");
  }
}

Noncompliant Code Example

This noncompliant code snippet is an example of unintentional object retention and is commonly called the Lapsed Listener. The button continues to hold a reference of the reader object even after completion of the readSomething method. Thus, the garbage collector will not collect the reader object. A similar problem occurs with inner classes as they hold an implicit reference to the outer class.

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
  }
}

To solve this problem, a matching pair of the removeActionListener should be used, as shown below. Unfortunately, this is not the panacea because an exception in the reader.readSomething method can change the control flow in such a way that the removeActionListener statement is never executed.

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) { /* handle exception */ }		 

Compliant Solution

The solution is to use the finally block in order 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 gets executed
}

Noncompliant Code Example

This example implements a stack data structure [[Bloch 08]] Item 6: Eliminate obsolete object references. The main issue is that it does not allow the garbage collector to de-allocate memory after the pop operation. The object references are retained even after the element is pop'ed. Such obsolete references are not garbage collected automatically. This can get even more deceitful since none of the objects referenced by the offending object get garbage collected either.

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);
    }
  }
}

Compliant Solution

This compliant solution assigns null values to all obsolete references. The garbage collector can now include this object in its list of objects to free. A NullPointerException exception results on subsequent attempts to access the particular object.

public Object pop() {
  if (size==0)
    throw new EmptyStackException(); // Ensures object consistency
  Object result = elements[--size];
  elements[size] = null; // Eliminate obsolete reference
  return result;
} 

While these examples may not model production scenarios, it is not uncommon to have obsolete references when dealing with data structures such as hash tables that contain many large-sized records. It is prudent to assign null to array-like custom data structures, however, doing so with individual objects or local variables gives no specific advantage. The garbage collector is sufficiently equipped to handle these cases. [[Commes 07]]

Noncompliant Code Example

A common variant of the aforementioned noncompliant code is the unintentional retention of objects while using a Map or a similar Collections object. In this example, a server maintains temporary metadata about all secure connections it commits to. Although the metadata is designed to be transient by nature, it persists even after the particular socket is closed. Unless some notification logic is installed, it is impossible to determine the best time to eliminate the object reference. Likewise, nulling out original objects or referents (Socket connections) by itself, proves to be unwieldy.

class HashMemLeak {
  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);  
  }	
}

Compliant Solution

This compliant solution uses weak references to ameliorate the issue. Strong references typically used in code, do not allow the garbage collector to reclaim the objects that are stored compositely, such as in a Map. According to [[API 06]], weak reference objects:

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed.

A referent is the object that is being referred to. As soon as any strong references to the object are found to have phased out, the garbage collector reclaims the referent. With WeakHashMap, the map's key is weakly referred to and thus determines whether the corresponding referents are ready to be collected.

// ...
private Map<SSLSocket, InetAddress> m = Collections.synchronizedMap(new WeakHashMap<SSLSocket, InetAddress>());
// ...

It is not enough to facilitate the collection of unneeded objects with weak references. It is critical to prune the data structure so that more entries can be accommodated in the newly created space. This can be achieved by calling the get() method of WeakHashMap and removing the entry that corresponds to the null return value (polling). A more efficient method is to use a reference queue. [[Goetz 05b]]

If the referent is assigned the value null, it will eventually be garbage collected. However, the hashmap will continue to strongly reference the WeakReference object and the corresponding value (for each entry in the hashmap). As soon as the GC clears the reference (which referred to the referent), it adds the corresponding WeakReference object to the reference queue. It remains there unless 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 collected. Alternatively, this two-step procedure can be carried out manually by using the following code:

ReferenceQueue queue = new ReferenceQueue();
WeakReference wr = new WeakReference(key, queue); // two-arg constructor, key = 'sock'
hashmap.put(wr, value);
while ((wr = (WeakReference) queue.poll()) != null) {
  hashmap.remove(wr); // removes the WeakReference object and the value (not the referent)
}

Note that the two-argument constructor of WeakReference takes a Queue argument and has to be used in order to perform direct queue processing.

It is also permissible to use soft references since they guarantee that the referent will be reclaimed before an OutOfMemoryError results but no sooner than the time when memory begins to run out.

Risk Assessment

Memory leaks in Java applications may be exploited, resulting in denial-of-service attacks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MSC01-J

low

unlikely

high

P1

L3

Automated Detection

TODO

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

References

[[API 06]] Class Vector, Class WeakReference
[[Gupta 05]]
[[Bloch 08]] Item 6: Eliminate obsolete object references
[[Commes 07]] Memory Leak Avoidance
[[Goetz 05]] Lapsed listeners
[[Goetz 05b]] "Memory leaks with global Maps" and "Reference queues"
[[MITRE 09]] CWE ID 401 "Failure to Release Memory Before Removing Last Reference (aka 'Memory Leak')"


MSC00-J. Eliminate class initialization cycles      49. Miscellaneous (MSC)      ENV01-J. Be aware of the JVM Tool Interface

  • No labels