Now that we’ve seen how to create objects, it’s time to talk about their destruction. If you’re accustomed to programming in C or C++, you’ve probably spent time hunting down memory leaks in your code. Java takes care of object destruction for you; you don’t have to worry about traditional memory leaks, and you can concentrate on more important programming tasks.[14]
Java uses a technique known as garbage collection to remove objects that are no longer needed. The garbage collector is Java’s grim reaper. It lingers in the background, stalking objects and awaiting their demise. It finds and watches them, periodically counting references to them to see when their time has come. When all references to an object are gone and it’s no longer accessible, the garbage-collection mechanism declares the object unreachable and reclaims its space back to the available pool of resources. An unreachable object is one that can no longer be found through any combination of “live” references in the running application.
Garbage collection uses a variety of algorithms; the Java virtual machine architecture doesn’t require a particular scheme. It’s worth noting, however, how some implementations of Java have accomplished this task. In the beginning, Java used a technique called “mark and sweep.” In this scheme, Java first walks through the tree of all accessible object references and marks them as alive. Java then scans the heap, looking for identifiable objects that aren’t marked. In this technique, Java is able to find objects on the heap because they are stored in a characteristic way and have a particular signature of bits in their handles unlikely to be reproduced naturally. This kind of algorithm doesn’t become confused by the problem of cyclic references, in which objects can mutually reference each other and appear alive even when they are dead (Java handles this problem automatically). This scheme wasn’t the fastest method, however, and caused pauses in the program. Since then, implementations have become much more sophisticated.
Modern Java garbage collectors effectively run continuously without forcing any lengthy delay in execution of the Java application. Because they are part of a runtime system, they can also accomplish some things that could not be done statically. Sun’s Java implementation divides the memory heap into several areas for objects with different estimated lifespans. Short-lived objects are placed on a special part of the heap, which reduces the time to recycle them drastically. Objects that live longer can be moved to other, less volatile parts of the heap. In recent implementations, the garbage collector can even “tune” itself by adjusting the size of parts of the heap based on the actual application performance. The improvement in Java’s garbage collection since the early releases has been remarkable and is one of the reasons that Java is now roughly equivalent in speed to traditional compiled languages.
In general, you do not have to concern yourself with the
garbage-collection process. But one garbage-collection method can be
useful for debugging. You can prompt the garbage collector to make a
clean sweep explicitly by invoking the System.gc()
method.
This method is completely implementation-dependent and may do nothing,
but it can be used if you want some guarantee that Java has cleaned up
before you do an activity.
Before an object is removed by garbage collection, its
finalize()
method is
invoked to give it a last opportunity to clean up its act and free other
kinds of resources it may be holding. While the garbage collector can
reclaim memory resources, it may not take care of things such as closing
files and terminating network connections as gracefully or efficiently
as could your code. That’s what the finalize()
method is for. An object’s finalize()
method is called once and only once
before the object is garbage-collected. However, there’s no guarantee
when that will happen. Garbage collection may, in theory, never run on a
system that is not short of memory. It is also interesting to note that
finalization and collection occur in two distinct phases of the
garbage-collection process. First, items are finalized; then they are
collected. It is, therefore, possible that finalization can
(intentionally or unintentionally) create a lingering reference to the
object in question, postponing its garbage collection. The object is, of
course, subject to collection later if the reference goes away, but its
finalize()
method isn’t called
again.
The finalize()
methods of
superclasses are not invoked automatically for you. If you need to
invoke the finalization routine of your parent classes, you should
invoke the finalize()
method of your
superclass, using super.finalize()
.
We discuss inheritance and overridden methods in Chapter 6.
In general, as we’ve described, Java’s garbage collector reclaims objects when they are unreachable. An unreachable object, again, is one that is no longer referenced by any variables within your application and that is not reachable through any chain of references by any running thread. Such an object cannot be used by the application any longer and is, therefore, a clear case where the object should be removed.
In some situations, however, it is advantageous to have Java’s
garbage collector work with your application to decide when it is time
to remove a particular object. For these cases, Java allows you to hold
an object reference indirectly through a special wrapper object, a type
of java.lang.ref.Reference
. If Java then decides
to remove the object, the reference the wrapper holds turns to null
automatically. While the reference
exists, you may continue to use it in the ordinary way and, if you wish,
assign it elsewhere (using normal references), preventing its garbage
collection.
There are two types of Reference
wrappers that implement different
schemes for deciding when to let their target references be
garbage-collected. The first is called a WeakReference
.
Weak references are eligible for garbage
collection immediately; they do not prevent garbage collection the way
that ordinary “strong” references do. This means that if you have a
combination of strong references and references contained in WeakReference
wrappers in your application,
the garbage collector waits until only WeakReference
s remain and then collects the
object. This is an essential feature that allows garbage collection to
work with certain kinds of caching schemes. You’ll often want to cache
an object reference for performance (to avoid creating it or looking it
up). But unless you take specific action to remove unneeded objects from
your cache, the cache keeps those objects alive forever by maintaining
live references to them. By using weak references, you can implement a
cache that automatically throws away references when the object would
normally be garbage-collected. In fact, an implementation of HashMap
called WeakHashMap
is provided that does just this
(see Chapter 11 for details).
The second type of reference wrapper is called SoftReference
. A
soft reference is similar to a weak reference,
but it tells the garbage collector to be less aggressive about
reclaiming its contents. Soft-referenced objects are collected only when
and if Java runs short of memory. This is useful for a slightly
different kind of caching where you want to keep some content around
unless there is a need to get rid of it. For example, a web browser can
use soft references to cache images or HTML strings internally, thus
keeping them around as long as possible until memory constraints come
into play. (A more sophisticated application might also use its own
scheme based on a “least recently used” marking of some kind.)
The java.lang.ref
package
contains the WeakReference
and
SoftReference
wrappers,
as well as a facility called ReferenceQueue
that allows your application to
receive a list of references that have been collected. It’s important
that your application use the queue or some other checking mechanism to
remove the Reference
objects
themselves after their contents have been collected; otherwise, your
cache will soon fill up with empty Reference
object wrappers.
[14] It’s still possible in Java to write code that holds onto objects forever, consuming more and more memory. This isn’t really a leak so much as it is hoarding memory. It is also usually much easier to track down with the correct tools and techniques.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.