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. Similarly, use of array-based data structures such as ArrayLists
can introduce a requirement to indicate the absence of an entry by explicitly setting its individual array element to null
.
Noncompliant Code Example (Removing Short-Lived Objects)
In this noncompliant code example, a long-lived ArrayList
contains references to both long- and short-lived elements. The programmer marks elements that have become irrelevant by setting a '"dead' " flag in the object.
Code Block | ||
---|---|---|
| ||
class DataElement { private boolean dead = false; // otherOther fields public boolean isDead() { return dead; } public void killMe() { dead = true; } } // elsewhereElsewhere ArrayList longLivedList = new ArrayList<DataElement>(...); // processingProcessing that renders an element irrelevant // Kill the element that is now irrelevant longLivedList.get(someIndex).killMe(); |
The garbage collector cannot collect the dead DataElement
object until it becomes unreferenced. Note that all methods that operate on objects of class DataElement
must check whether the instance in hand is dead.
Compliant Solution (Set Reference to null
)
In this compliant solution, rather than use a dead flag, the programmer assigns null
to ArrayList
elements that have become irrelevant.
Code Block | ||
---|---|---|
| ||
class DataElement { // deadDead flag removed // otherOther fields } // elsewhereElsewhere ArrayList longLivedList = new ArrayList<DataElement>(...); // processingProcessing that renders an element irrelevant // setSet the reference to the irrelevant DataElement to null longLivedList.set(someIndex, null); |
Note that all code that operates on the longLivedList
must now check for list entries that are null.
Compliant Solution (Use Null Object Pattern)
This compliant solution avoids the problems associated with intentionally null references by using a singleton sentinel object. This technique is known as the Null Object pattern (also as the Sentinel pattern). When feasible, programmers should choose this design pattern over the explicit null reference values.
Code Block | ||
---|---|---|
| ||
class DataElement { public static final DataElement NULL = createSentinel(); // deadDead flag removed // otherOther fields private static final DataElement createSentinel() { // allocateAllocate a sentinel object, setting all its fields // to carefully chosen "do nothing" values } } // elsewhereElsewhere ArrayList longLivedList = new ArrayList<DataElement>(...); // processingProcessing that renders an element irrelevant // setSet the reference to the irrelevant DataElement to // the NULL object longLivedList.set(someIndex, NULL); |
When using this pattern, the NULL
object must be a singleton and must be final. It may be either public or private, depending on the overall design of the DataElement
class. The state of the NULL
object should be immutable after creation; immutability can be enforced either by using final
fields or by explicit code in the methods of the DataElement
class. See [Grand 2002] Chapter 8, "Behavioral Patterns, the Null Object," of Patterns in Java, Vol. 1, second edition [Grand 2002], for additional information on this design pattern, and also ERR08-J. Do not catch NullPointerException or any of its ancestors.
Applicability
Leaving short-lived objects in long-lived container objects may consume memory that cannot be recovered by the garbage collector, thereby leading to memory exhaustion and possible denial of service attacks.
Bibliography
Chapter 8, "Behavioral Patterns, the Null Object" |