© Kishori Sharan 2018
Kishori SharanJava Language Featureshttps://doi.org/10.1007/978-1-4842-3348-1_11

11. Garbage Collection

Kishori Sharan
(1)
Montgomery, Alabama, USA
 
In this chapter, you will learn:
  • What garbage collection is
  • How garbage collection is implemented in Java
  • How to pass a hint to the JVM to run the garbage collector
  • How to implement the finalizers
  • Different states of an object based on its reachability and finalization status
  • The difference between strong and weak references
  • How to use weak references to implement memory-sensitive cache
  • How to use PhantomReference and ReferenceQueue classes to implement cleanup tasks for objects
  • How to use the new Cleaner class in JDK9 to perform cleanup work for phantom reachable objects
All example programs in this chapter are members of the jdojo.gc module, as declared in Listing 11-1.
// module-info.java
module jdojo.gc {
    exports com.jdojo.gc;
}
Listing 11-1.
The Declaration of a jdojo.gc Module

What Is Garbage Collection?

In a programming language, memory management is central to the development of a fast, efficient, and bug-free application. Memory management involves two activities:
  • Memory allocation
  • Memory reclamation
When a program needs memory, memory is allocated from a memory pool. When the program is finished with the memory, the memory is returned to the memory pool, so it can be reused by some other part of the program in the future. The process of returning memory to the pool is known as memory reclamation or memory recycling. The memory allocation and reclamation can be accomplished explicitly or implicitly.
In explicit memory allocation , the programmer decides how much memory is needed. The programmer requests that amount of memory from the program runtime environment, known as the memory allocator or simply the allocator. The allocator allocates the requested memory and marks that memory as in-use, so it will not allocate the same memory block again. Here, we assumed that our request for new memory block to allocator is always fulfilled. This can happen only if we have an infinite amount of memory. However, that is not the case with any computer. Some computers may have megabytes of memory and some may have gigabytes. However, there is always a limit to the memory available on a computer. If we run a program that always allocates memory blocks from the memory pool and never returns the memory back to the pool, we will soon run out of memory and the program will stop.
In explicit memory reclamation, the programmer decides when to return the memory to the memory pool. The allocator is free to allocate the returned memory when it receives a new request for memory allocation. Explicit memory reclamation often leads to subtle bugs in programs. It also complicates the inter-modules interface design. Suppose there are two modules in an application and they are named m1 and m2. Module m1 allocates a block of memory and the reference to that memory is r1. Module m1 makes a call to module m2, passing the reference r1. Module m2 stores the reference r1 for future use. Which module should be responsible for the reclamation of the memory referenced by r1? There could be different scenarios depending on the program flow between the two modules. Suppose module m1 reclaims the memory immediately after a call to module m2. In such a case, you may come across two problems:
  • At some point in the program execution, module m2 tries to access the memory using the reference r1. Because module m1 has already reclaimed the memory referenced by r1, the same memory might have been reallocated by the allocator and may have entirely different data stored at that memory location. In such a case, r1 is called a dangling reference because it is referencing a memory location that has already been reclaimed. If you try to read data using a dangling reference, the result would be unpredictable. You cannot have a dangling reference in Java.
  • Module m1 may try to use reference r1 after it has reclaimed the memory referenced by r1. This will also lead to the problem of using a dangling reference.
If module m2 reclaims the memory referenced by r1, you may end up with the same dangling reference problem if any of the modules, m1 or m2, try to use reference r1. What happens if none of the modules reclaims the memory and never uses the reference r1 again? The memory will never be returned to the memory pool and will never be reused. This situation is known as a memory leak because the allocator has no knowledge of the memory block, which is not returned to it, even though it is never used again by the program. If memory leaks happen regularly, the program will eventually run out of memory and will cease to function. If your program runs for a short time with small memory leaks, you may not even notice this bug for years or for the entire life of your program!
In a programming language that allows explicit memory management, programmers spend a substantial amount of effort in the memory management aspect of the program. In another kind of memory-related problem, a programmer may allocate a big amount of memory statically, so that he can use it throughout the lifecycle of the program. The static memory allocation may not always succeed, since static memory has an upper limit. The hardest part of the memory management decision is to decide when to reclaim the memory to avoid dangling references and memory leaks.
In implicit memory allocation, a programmer indicates to the runtime system that he wants to allocate memory to store a particular type of data. The runtime system computes the memory needed to store the requested type of data and allocates it to the running program. In implicit/automatic memory reclamation, a programmer does not need to worry about memory reclamation. The runtime system will automatically reclaim all memory blocks, which will never be used by the program again. The process of automatic reclamation of unused memory is known as garbage collection . The program that performs garbage collection is known as a garbage collector or simply a collector. The garbage collector may be implemented as part of the language runtime system or as an add-on library.

Memory Allocation in Java

In Java, programmers deal with objects. The memory required for an object is always allocated on the heap. The memory is allocated implicitly using the new operator. Suppose you have a class called Employee. You create an object of the Employee class.
Employee emp = new Employee();
Depending on the definition of the Employee class, the Java runtime computes how much memory is needed, allocates the needed memory on heap, and stores the reference to that memory block in the emp reference variable. Note that when you want to create an Employee object, you do not specify how much memory you need. The new Employee() part of the previous statement indicates to Java that you want to create an object of the Employee class. Java queries the definition of the Employee class to compute the memory required to represent an Employee object.
Every Java object in memory has two areas: a header area and a data area. The header area stores bookkeeping information to be used by the Java runtime, for example, the pointer to the object’s class, object’s garbage collection status, object’s locking information, length of an array if the object is an array, etc. The data area is used to store the values of all instance variables of the object. The header area layout is fixed for a particular JVM implementation, whereas the data area layout is dependent on the object type. The Java Hotspot virtual machine uses two machine-words (in 32-bit architecture one word is 4 bytes) for the object header. If the object is an array, it uses three machine-words for its header. One extra word in the header is used to store the value of the array’s length. However, most JVMs use three machine-words for an object’s header. Figure 11-1 depicts the object layout for the Java Hotspot VM.
A323070_2_En_11_Fig1_HTML.gif
Figure 11-1.
The layout of an object in the Java Hotspot VM
The Java Hotspot VM uses a variable length object header to save memory on the heap. Since most Java objects are small, one machine-word savings per object for non-array objects is a significant heap space savings. The Java Hotspot VM’s object header contains the following information:
  • classptr: This is the first machine-word in the object layout. It contains a pointer to the class information of the object. The class information includes the object’s method table, the object’s size, and a pointer to a Class structure, which contains information about the class of the object, etc.
  • hash + age + lock: This is the second machine-word in the object header. It contains the object’s hash code, age information, and lock fields. Age information is used in the process of reclaiming the object’s memory by the generational garbage collector. The generation garbage collector is a special type of garbage collector that uses the object’s age in its algorithm to reclaim an object’s memory.
  • arraylength: This is the third machine-word in the object header. It is included only if the object is an array. It contains the length of the array. In this case, the object’s data area contains the array elements.
Note
In Java, all objects are created on heap. Java uses the new operator to allocate memory for an object on heap. An array’s length is not part of its class definition. It is defined at runtime. It is stored in the object header. You will not find the length instance variable in the array’s class definition when you perform introspection on an array’s object.
Java does not provide any direct means to compute the size of an object. You should not write a Java program that depends on the size of the objects anyway. The size of primitive types—for example, int, long, double, etc.—is fixed for all JVM implementations. The layout and size of an object depend on the JVM implementation. Therefore, any code that depends on the size of objects may work on one platform and not on others.

Garbage Collection in Java

The garbage collector is part of the Java platform. It runs in the background in a low priority thread. It automatically reclaims objects. However, before it reclaims objects, it makes sure that the running program in its current state will never use them again. This way, it ensures that the program will not have any dangling references. An object that cannot be used in the future by the running program is known as a dead object or garbage. An object that can be used in the future by the running program is known as a live object.
There are many algorithms to determine whether an object is live or dead. One of the simplest, but not very efficient, algorithms is based on reference counting, which stores the count of references that refer to an object. When an object’s reference is assigned to a reference variable, the reference count is incremented by 1. When a reference variable no longer refers to an object, the reference count is decremented by 1. When the reference count for an object is zero, it becomes garbage (or dead). This algorithm has a lot of overhead of updating the reference count of objects. Another type of algorithm, which is called a tracing algorithm , is based on the concept of a root set. A root set includes the following:
  • Reference variables in the Java stack for each thread
  • static reference variables defined in loaded classes
  • Reference variables registered using the Java Native Interface (JNI)
A garbage collector, which is based on the tracing algorithm, starts traversing references starting from the root set. Objects that can be reached (or accessed) from the reference variables in the root set are known as reachable objects . A reachable object is considered live. A reachable object from the root set may refer to other objects. These objects are also considered reachable. Therefore, all objects that can be reached directly or indirectly from the root set reference variables are considered live. Other objects are considered dead and are thus eligible for garbage collection.
An object may manage resources other than memory on heap. These resources may include network connections, file handles, memory managed explicitly by native code, etc. For example, an object may open a file when the object is created. File handles that can be opened simultaneously may have an upper limit depending on your operating system. When the object is garbage collected, you may want to close those file handles. The garbage collector gives the dying object a chance to perform the cleanup work. It does this by executing a predefined block of code before the memory for the dying object is reclaimed. The process of performing the cleanup work, before the object is reclaimed by the garbage collector, is known as finalization . The block of code that is invoked by the garbage collector to perform finalization is known as the finalizer. In Java, you can define an instance method called finalize() in a class, which serves as a finalizer for the objects of that class. The Java garbage collector invokes the finalize() method of an object before it reclaims the memory occupied by the object.

Invoking the Garbage Collector

Programmers have little control over the timing when the garbage collection is run. The JVM performs the garbage collection whenever it runs low on memory. The JVM tries its best to free up memory of all unreachable objects before it throws a java.lang.OutOfMemoryError error. The gc() method of the java.lang.Runtime class may be used to pass a hint to the JVM that it may run the garbage collection. The call to the gc() method is just a hint to the JVM. The JVM is free to ignore the call. Suggesting that the garbage collection should run can be invoked as shown:
// Get runtime instance and invoke the garbage collection
Runtime.getRuntime().gc();
The System class contains a convenience method called gc(), which is equivalent to executing the previous statement. You can also use the following statement to run the garbage collector:
// Invoke the garbage collection
System.gc();
The program in Listing 11-2 demonstrates the use of the System.gc() method . The program creates 2,000 objects of the Object class in the createObjects() method. The references of the new objects are not stored. You cannot refer to these objects again, and hence, they are garbage. When you invoke the System.gc() method, you suggest the JVM that it should try to reclaim the memory used by these objects. The memory freed by the garbage collector is displayed in the output section. Note that you will more than likely get a different output when you run this program. The freeMemory() method of the Runtime class returns the amount of free memory in the JVM.
// InvokeGC.java
package com.jdojo.gc;
public class InvokeGC {
    public static void main(String[] args) {
        long m1, m2, m3;
        // Get a runtime instance
        Runtime rt = Runtime.getRuntime();
        for (int i = 0; i < 3; i++) {
            // Get free memory
            m1 = rt.freeMemory();
            // Create some objects
            createObjects(2000);
            // Get free memory
            m2 = rt.freeMemory();
            // Invoke garbage collection
            System.gc();
            // Get free memory
            m3 = rt.freeMemory();
            System.out.println("m1 = " + m1 + ", m2 = " + m2 + ", m3 = "
                    + m3 + " Memory freed by gc() = " + (m3 - m2));
            System.out.println("-------------------------");
        }
    }
    public static void createObjects(int count) {
        for (int i = 0; i < count; i++) {
            // Do not store the references of new objects, so they become
            // eligible for garbage collection immediately.
            new Object();
        }
    }
}
m1 = 130188712, m2 = 130188712, m3 = 7402320
Memory freed by gc() = -122786392
-------------------------
m1 = 6225808, m2 = 6225808, m3 = 7241760
Memory freed by gc() = 1015952
-------------------------
m1 = 7207408, m2 = 7207408, m3 = 7241832
Memory freed by gc() = 34424
-------------------------
Listing 11-2.
Invoking Garbage Collection
In general, it is not advisable to invoke the garbage collector programmatically. Invoking the garbage collector has some overhead. It may slow down performance if it is invoked arbitrarily. The Java runtime takes care of reclaiming unused object’s memory automatically. You may get an OutOfMemoryError in your program. This error may be caused by many reasons. The Java runtime makes all efforts to free up memory, invoking the garbage collector before throwing the OutOfMemoryError error. Therefore, simply invoking the garbage collector programmatically will not make this error go away. To resolve this error, you can look at the following:
  • Review your program to make sure that you are not holding onto some object references that you will never use again. Set these references to null after you are done with them. Setting all references to an object to null makes the object eligible for the garbage collection. If you are storing large objects in static variables, those objects will remain in memory until the class itself is unloaded. Generally, the objects stored in static variables will take up memory forever. Review your program and try to avoid storing large objects in static variables.
  • Review your code and make sure that you are not caching large amounts of data in objects. You can use weak references to cache large amounts of data in objects. Weak references have an advantage over regular references (regular references are also known as strong references), in that the objects referenced by weak references are garbage collected before the Java runtime throws an OutOfMemoryError . I discuss weak references later in this chapter.
  • If none of these solutions works for you, you may try to adjust the heap size.

Object Finalization

Finalization is an action that is automatically performed on an object before the memory used by the object is reclaimed by the garbage collector. The block of code that contains the action to be performed is known as a finalizer. The Object class has a finalize() method , which is declared as
protected void finalize() throws Throwable
Because all Java classes inherit from the Object class, the finalize() method can be invoked on all Java objects. Any class can override and implement its own version of the finalize() method. The finalize() method serves as a finalizer for Java objects. That is, the garbage collector automatically invokes the finalize() method on an object before reclaiming the object’s memory. Understanding the correct use of the finalize() method is key to writing a good Java program, which manages resources other than the heap memory.
Note
The finalize() method in the Object class has been deprecated since JDK9. Use other ways clean up resources held by an object. I discuss them in this chapter. I also discuss how to use the finalize() method, even though it is deprecated, for the sake of completeness.
Let’s first start with a simple example that demonstrates the fact that the finalize() method is called before an object is garbage collected. Listing 11-3 defines a finalize() method in the Finalizer class. I used the @SuppressWarnings("deprecation") annotation on the finalize() method to suppress the compile-time deprecation warning because the method has been deprecated in JDK9.
// Finalizer.java
package com.jdojo.gc;
public class Finalizer {
    // id is used to identify the object
    private final int id;
    // Constructor which takes the id as argument
    public Finalizer(int id){
        this.id = id;
    }
    // This is the finalizer for the object. The JVM will call this method,
    // before the object is garbage collected    
    @SuppressWarnings("deprecation")
    @Override
    public void finalize(){
        // Just print a message indicating which object is being garbage collected.
        // Print message when id is a multiple of 100 just to avoid a bigger output.
        if (id % 100 == 0) {
            System.out.println ("finalize() called for " + id ) ;    
        }    
    }
    public static void main(String[] args) {
        // Create 500000 objects of the Finalizer class
        for(int i = 1; i <= 500000; i++){
            // Do not store reference to the new object
            new Finalizer(i);
        }
        // Invoke the garbage collector
        System.gc();
    }    
}
finalize() called for 63700
finalize() called for 186000
finalize() called for 185000
finalize() called for 184400
...
Listing 11-3.
Using the finalize() Method
The finalize() method prints a message if the object being garbage collected has an ID that’s a multiple of 100. The main() method creates 500,000 objects of the Finalizer class and calls System.gc() to invoke the garbage collector.
When the garbage collector determines that an object is unreachable, it marks that object for finalization and places that object in a queue. If you want the Java runtime to finalize all objects that are pending finalization, you can do so by calling the runFinalization() method of the Runtime class as shown:
Runtime rt = Runtime.getRuntime();
rt.runFinalization();
The System class has a runFinalization() convenience method, which is equivalent to calling the runFinalization() method of the Runtime class. It can be called as shown:
System.runFinalization();
Invoking the runFinalization() method is only a hint to the Java runtime to invoke the finalize() method of all objects pending finalization. Technically, you may call the finalize() method on an object in your code as many times as you want. However, it is meant for the garbage collector to call an object’s finalize() method at most one time during the lifetime of the object. The garbage collector’s one-time call to the finalize() method of an object is not affected by the fact that the finalize() method of the object was called programmatically before.
Programmers should not override the finalize() method in a class trivially. A finalize() method with no code, or one which calls the finalize() method of the Object class, is an example of a trivially overridden finalize() method. The method in the Object class does nothing. If your class is a direct subclass of the Object class and you do not have any meaningful code in the finalize() method of your class, it is better not to include the finalize() method in your class at all. Memory reclamation is faster and sooner for the objects, which do not have an implementation of the finalize() method compared to those that have an implementation of the finalize() method.

Finally or Finalize?

The timing of object finalization is not guaranteed. Finalization of all unreachable objects is also not guaranteed. In short, there is no guarantee when the finalize() method of an unreachable object will be called or if it will be called at all. So, what good is the finalize() method? The main purpose of a garbage collector in Java is to relieve programmers from the burden of freeing the memory of unused objects to avoid the problem of memory leaks and dangling references. Its secondary job is to run the finalization on the objects with no guarantee about the timing. As a programmer, you should not depend much on the finalization process of garbage collection. You should not code the finalize() method or code it with care. If you need to clean up resources for sure when you are done with them, you may use a try-finally block. If your resources are AutoCloseable, you may use a try-with-resources block. A try-finally block works as follows:
try {
    /* Get your resources and work with them */
} finally {
    /* Release your resources */
}
You can acquire resources and use them in a try block and release them in the associated finally block. A finally block is guaranteed to be executed after a try block is executed. This way, you can be sure that scarce resources in your program are always freed once you are done with them. However, it may not always be feasible, because of performance issues, to release resources immediately after you are done with them. For example, you may not want to open a network connection every time you need it. You may open a network connection once, use it, and close it when you no longer need it. Sometimes you may not know the exact point in a program from where you will not need that network connection. In such cases, you can code the finalize() method as a backup to free the resources if they have not been freed yet. You can call the finalize() method programmatically when you know for sure that the resources can be freed. Listing 11-4 contains the code for a FinalizeAsBackup class that shows the skeleton of the code that uses such a technique.
// FinalizeAsBackup.java
package com.jdojo.gc;
public class FinalizeAsBackup {
    /* Other codes go here */
    SomeResource sr;
    public void aMethod() {
        sr = Obtain the resources here...;
        /* Do some processing . . . */
        /* Note the conditional freeing of resources */
        if (some condition is true) {
            /* Free resources here calling finalize() */
            this.finalize();
        }
    }
    public void finalize() {
        /* Free the resources if they have not been freed yet */
        if (resources not yet freed ) {
            free resources now;
        }
    }
}
Listing 11-4.
Template of a Class that Uses the finalize() Method as a Backup to Free Resources
The aMethod() method of the class gets the resource and stores its reference in the sr instance variable. Programmers call the finalize() method when they are sure they should free the resources. Otherwise, the garbage collector will call the finalize() method and resources will be freed. Note that the FinalizeAsBackup class is a template. It contains pseudocode to explain the technique. This class will not compile.
Tip
The moral of the story about using the finalize() method is to not use it or use it with care and use it only as a last resort to free resources. You can use a try-finally block to free resources. The order in which objects are finalized is not defined. For example, if object obj1 becomes eligible for garbage collection before object obj2, it is not guaranteed that obj1 will be finalized before obj2. When an uncaught exception is thrown, the main program is halted. However, an uncaught exception in a finalizer halts the finalization of only that object, not the entire application.

Object Resurrection

Someone is about to die. God asks him for his last wish. He says, “Give me my life back.” God grants his last wish and he gets back his life. When he was about to die the second time God kept quiet and let him die without asking him for his last wish. Otherwise, he would ask for his life repeatedly and he would never die.
The same logic applies to an object’s finalization in Java. The call to the finalize() method of an object is like the garbage collector asking the object for its last wish. Generally, the object responds, “I want to clean up all my mess.” That is, an object responds to its finalize() method call by performing some cleanup work. It may respond to its finalize() method call by resurrecting itself by placing its reference in a reachable reference variable. Once it is reachable through an already reachable reference variable, it is back to life. The garbage collector marks an object using the object’s header bits as finalized, after it calls the object’s finalize() method. If an already finalized object becomes unreachable the next time during garbage collection, the garbage collector does not call the object’s finalize() method again.
The resurrection of an object is possible because the garbage collector does not reclaim an object’s memory just after calling its finalize() method. After calling the finalize() method, it just marks the object as finalized. In the next phase of the garbage collection, it determines again if the object is reachable. If the object is unreachable and finalized, only then will it reclaim the object’s memory. If an object is reachable and finalized, it does not reclaim object’s memory; this is a typical case of resurrection.
Resurrecting an object in its finalize() method is not a good programming practice. One simple reason is that if you have coded the finalize() method, you expect it to be executed every time an object dies. If you resurrect the object in its finalize() method, the garbage collector will not call its finalize() method again when it becomes unreachable a second time. After resurrection, you might have obtained some resources that you expect to be released in the finalize() method. This will leave subtle bugs in your program. It is also hard for other programmers to understand your program flow if your program resurrects objects in their finalize() methods. Listing 11-5 demonstrates how an object can be resurrected using its finalize() method.
// Resurrect.java
package com.jdojo.gc;
public class Resurrect {
    // Declare a static variable of the Resurrect type
    private static Resurrect res = null;
    // Declare an instance variable that stores the name of the object
    private String name = "";
    public Resurrect(String name) {
        this.name = name;
    }
    public static void main(String[] args) {
        // We will create objects of the Resurrect class and will not store
        // their references, so they are eligible for garbage collection immediately.
        for (int count = 1; count <= 1000; count++) {
            new Resurrect("Object #" + count);
            // For every 100 objects created invoke garbage collection
            if (count % 100 == 0) {
                System.gc();
                System.runFinalization();
            }
        }
    }
    public void sayHello() {
        System.out.println("Hello from " + name);
    }
    public static void resurrectIt(Resurrect r) {
        // Set the reference r to static variable res, which makes it reachable
        // as long as res is reachable.
        res = r;
        // Call a method to show that we really got the object back
        res.sayHello();
    }
    @SuppressWarnings("deprecation")
    @Override
    public void finalize() {
        System.out.println("Inside finalize(): " + name);
        // Resurrect this object
        Resurrect.resurrectIt(this);
    }
}
Inside finalize(): Object #82
Hello from Object #82
Inside finalize(): Object #100
Hello from Object #100
Inside finalize(): Object #99
Hello from Object #99
...
Listing 11-5.
Object Resurrection
The Resurrect class creates 1,000 objects in the main() method. It does not store references of those new objects, so they become garbage as soon as they are created. After creating 100 new objects, it invokes the garbage collector using the System.gc() method. It also calls the System.runFinalization() method, so the finalizers are run for the garbage objects. When the garbage collector calls the finalize() method for an object, that object passes its reference to the resurrectIt() method. This method stores the dying object’s reference in the static variable res, which is reachable. The method resurrectIt() also calls the sayHello() method on the resurrected object to show which object was resurrected. Note that once another object resurrects itself, you are overwriting the static res variable with the recently resurrected object reference. The previously resurrected object becomes garbage again. The garbage collector will reclaim the memory for the previously resurrected object without calling its finalize() method again. You may get different output when you run the program.

State of an Object

The state of a Java object is defined based on two criteria:
  • Finalization status
  • Reachability
Based on the finalization status, an object can be in one of the following three states:
  • Unfinalized
  • Finalizable
  • Finalized
When an object is instantiated, it is in the unfinalized state. For example,
Employee john = new Employee();
The object referred to by the john reference variable is in an unfinalized state after this statement is executed. The finalizer of an unfinalized object had never been invoked automatically by the JVM. An object becomes finalizable when the garbage collector determines that the finalize() method can be invoked on the object. A finalized object has its finalize() method invoked automatically by the garbage collector.
Based on reachability, an object can be in one of three states:
  • Reachable
  • Finalizer-reachable
  • Unreachable
An object is reachable if it can be accessed through any chain of references from the root set. A finalizer-reachable object can be reached through the finalizer of any finalizable object. A finalizer-reachable object may become reachable if the finalizer from which it is reachable stores its reference in an object that is reachable. This is the situation when an object resurrects. An object may resurrect itself in its finalize() method or through another object’s finalize() method. An unreachable object cannot be reached by any means.
There are nine combinations of object states based on their finalization status and reachability status. One of the nine combinations, finalizable and unreachable, is not possible. The finalize() method of a finalizable object may be called in the future. The finalize() method can still refer to the object using the this keyword. Therefore, a finalizable object cannot also be unreachable. An object can exist in one of the following eight states:
  • Unfinalized - Reachable
  • Unfinalized - Finalizer-reachable
  • Unfinalized - Unreachable
  • Finalizable - Reachable
  • Finalizable - Finalizer-reachable
  • Finalized - Reachable
  • Finalized - Finalizer-reachable
  • Finalized - Unreachable

Weak References

The concept of weak references in the context of garbage collection is not new to Java. It existed before in other programming languages. So far, the object references I have discussed are strong references. That is, as long as the object reference is in scope, the object it refers to cannot be garbage collected. For example, consider the following object creation and reference assignment statement:
Employee john = new Employee("John Jacobs");
Here, john is a reference to the object created by the expression new Employee("John Jacobs"). The memory state that exists after executing this statement is depicted in Figure 11-2.
A323070_2_En_11_Fig2_HTML.gif
Figure 11-2.
An example of a strong reference
If at least one strong reference to an object exists, the garbage collector will not reclaim that object. In the previous section, I discussed the object state based on its reachability. By stating that there is a strong reference to an object, I mean that the object is reachable. With the introduction of weak references, now there are three more states of an object based on its reachability:
  • Softly reachable
  • Weakly reachable
  • Phantom reachable
Therefore, when I called an object reachable in the last section, I will call it strongly reachable now onward. This change in terminology is because of the introduction of three new kinds of object reachability. Before I discuss the three new kinds of object reachability, you need to know about the classes included in the java.lang.ref package. There are four classes of interest, as shown in Figure 11-3. I do not discuss the Reference class from the diagram.
A323070_2_En_11_Fig3_HTML.jpg
Figure 11-3.
A class diagram for some classes in the java.lang.ref package
Reference<T> is an abstract class and it is the superclass for the SoftReference<T>, WeakReference<T>, and PhantomReference<T> classes. They are generic classes; their type parameter T is the type of object they reference. The SoftReference, WeakReference, and PhantomReference classes are used to create weak references. Note that by the phrase “weak reference,” I mean a reference that is not a strong reference. By the phrase WeakReference, I mean the java.lang.ref.WeakReference class. The ReferenceQueue class is used to place the references of SoftReference, WeakReference, and PhantomReference objects in a queue. Let’s look at different ways to create these three types of objects. The constructors for these three classes are shown in Table 11-1.
Table 11-1.
Constructors for the SoftReference, WeakReference, and PhantomReference Classes
Class
Constructors
SoftReference<T>
SoftReference(T referent)
SoftReference(T referent, ReferenceQueue<? super T> q)
WeakReference<T>
WeakReference(T referent)
WeakReference(T referent, ReferenceQueue<? super T> q)
PhantomReference<T>
PhantomReference(T referent, ReferenceQueue<? super T> q)
You can create an object of the SoftReference class as follows:
Employee john = new Employee ("John Jacobs");
SoftReference<Employee> sr = new SoftReference<>(john);
The memory state after executing these two statements is depicted in Figure 11-4.
A323070_2_En_11_Fig4_HTML.gif
Figure 11-4.
An example of a soft reference
In Figure 11-4, there are two strong references and one soft reference. All three weak reference classes have two instance variables: referent and queue. They are used to hold the reference of the object and reference queue passed in to the constructors of these classes. A reference to any object stored in the referent instance variable of any of these three classes is known as a weak reference in general—and a soft reference, weak reference, or phantom reference in particular—depending on the class being used. Therefore, the link from a soft reference object to the employee object shown in Figure 11-4 is a weak reference. To be specific, I call it a soft reference because I used an object of the SoftReference class. Any reference that does not involve the referent instance variable of any of these three classes is a strong reference in Java. Therefore, john and sr are strong references.
How are weak references different from strong references ? The difference lies in how the garbage collector treats them. Weak references do not prevent the objects they reference from being collected by the garbage collector. That is, if there is a weak reference to an object, the garbage collector can still reclaim the object. However, if there is at least one strong reference to an object, the garbage collector will not reclaim the object. Before you start looking at details of how to use these three reference classes, let’s discuss the reachability of an object when these classes are involved in a program.
  • Strongly reachable : An object is strongly reachable if it can be reached from the root set through at least one chain of references, which does not involve any weak reference.
  • Softly reachable : An object is softly reachable if it is not strongly reachable and it can be reached from the root set through at least one chain of references, which involves at least one soft reference, but no weak and phantom references.
  • Weakly reachable : An object is weakly reachable if it is not strongly and softly reachable and it can be reached from the root set through at least one chain of references, which involves at least a weak reference and no phantom references.
  • Phantom reachable : An object is phantom reachable if it is not strongly, softly, and weakly reachable and it can be reached from the root set through at least one chain of references, which involves at least a phantom reference. A phantom reachable object is finalized, but not reclaimed.
Among the three kinds of weak references, a soft reference is considered stronger than a weak reference and a phantom reference. A weak reference is considered stronger than a phantom reference. Therefore, the rule to identify the reachability of an object is that if an object is not strongly reachable, it is as reachable as the weakest reference in the reference chain leading to that object. That is, if a chain of references to an object involves a phantom reference, the object must be phantom reachable. If a chain of references to an object does not involve a phantom reference, but it involves a weak reference, the object must be weakly reachable. If a chain of references to an object does not involve a phantom reference and a weak reference, but it involves a soft reference, the object must be softly reachable.
How do you determine the reachability of an object when there is more than one chain of references to the object? In such cases, you determine the object’s reachability using all possible chains of references and use the strongest one. That is, if an object is softly reachable through one chain of references and phantom reachable through another, the object is considered softly reachable. Figure 11-5 depicts the examples of how an object’s reachability is determined. The elliptical shape at the end of every reference chain represents an object. The reachability of the object has been indicated inside the elliptical shape. The rectangles denote references.
A323070_2_En_11_Fig5a_HTML.gifA323070_2_En_11_Fig5b_HTML.gif
Figure 11-5.
Different kinds of an object’s reachability

Accessing and Clearing a Referent’s Reference

This section uses objects of a trivial class to demonstrate the use of reference classes. This class, called BigObject, is shown in Listing 11-6. It has a big array of long as an instance variable, so it uses a big chunk of memory. The id instance variable is used to track the objects of this class. The finalize() method prints a message on the console using the object’s id.
// BigObject.java
package com.jdojo.gc;
public class BigObject {
    // Declare a big array of with room for 20480 long elements.
    private final long[] anArray = new long[20480];
    // Have an id to track the object
    private final long id;
    public BigObject(long id) {
        this.id = id;
    }
    // Define finalize() to track the object's finalization
    @SuppressWarnings("deprecation")
    @Override
    public void finalize() {
        System.out.println("finalize() called for id: " + id);
    }
    @Override
    public String toString() {
        return "BigObject: id = " + id;
    }
}
Listing 11-6.
A BigObject Class , Which Uses Big Memory
The object that you pass to the constructors of the WeakReference, SoftReference, and PhantomReference classes is called a referent. In other words, the object referred to by the object of these three reference classes is called a referent . To get the reference of the referent of a reference object, you need to call the get() method.
// Create a big object with id as 101
BigObject bigObj = new BigObject(101);
/* At this point, the big object with id 101 is strongly reachable */
// Create a soft reference object using bigObj as referent
SoftReference<BigObject> sr = new SoftReference<>(bigObj);
/* At this point, the big object with id 101 is still strongly reachable, because bigObj
   is a strong reference referring to it. It also has a soft reference referring to it.
*/
// Set bigObj to null to make the object softly reachable
bigObj = null;
/* At this point, the big object with id 101 is softly reachable, because
   it can be reached only through a soft reference sr.
*/
// Get the reference of referent of soft reference object
BigObject referent = sr.get();
/* At this point, the big object with id 101 again becomes strongly reachable because
   referent is a strong reference. It also has a soft reference referring to it.
*/
Figure 11-6 depicts the memory states with all the references after you execute each statement in the previous snippet of code.
A323070_2_En_11_Fig6_HTML.gif
Figure 11-6.
Accessing the referent of a reference object
The clear() method clears the link between the reference (weak, soft, or phantom) object and its referent. The following piece of code illustrates its use:
// Create a soft reference object. Use a BigObject with id 976 as its referent.
SoftReference<BigObject> sr1 = new SoftReference<>(new BigObject(976));
/* At this point, the BigObject with id 976 is softly reachable, because it is reachable
   only through a soft reference sr.
*/
// Clear the referent
sr1.clear();
/* At this point, the big object with id 976 is unreachable (to be exact, it is
   finalizer-reachable), because we cleared the only one reference (soft reference)
   we had to the object.
*/
The memory state with all references, after each statement in the previous snippet of code is executed, is depicted in Figure 11-7 . After the referent’s reference is cleared using the clear() method, the get() method returns null. Note that the get() method of a PhantomReference object always returns null.
A323070_2_En_11_Fig7_HTML.gif
Figure 11-7.
Clearing a referent

Using the SoftReference Class

A softly reachable object is used to maintain memory-sensitive caches. That is, if you want to maintain a cache of objects as long as the program is not running low on memory, you can use softly reachable objects. When the program runs low on memory, the garbage collector clears the soft references to an object, making the object eligible for reclamation. At that point, your program will lose some or all of its objects from the cache. Java does not guarantee that soft references will not be cleared if the program is not running low on memory. However, it guarantees that all soft references will be cleared before the JVM throws an OutOfMemoryError. There is also no guarantee of the order in which soft references will be cleared. However, JVM implementations are encouraged to clear the least-recently created/used soft reference first. Listing 11-7 shows the wrong use of soft references to cache data.
// WrongSoftRef.java
package com.jdojo.gc;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
public class WrongSoftRef {
    public static void main(String[] args) {
        // Create a big object with an id 101 for caching
        BigObject bigObj = new BigObject(101);
        // Wrap soft reference inside a soft reference
        SoftReference<BigObject> sr = new SoftReference<>(bigObj);
        // Let us try to create many big objects storing their
        // references in an array list, just to use up big memory.
        ArrayList<BigObject> bigList = new ArrayList<>();
        long counter = 102;
        while (true) {
            bigList.add(new BigObject(counter++));
        }
    }
}
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
Listing 11-7.
An Incorrect Use of a Soft Reference
The intention of the programmer was to cache a big object with an ID of 101 using a soft reference . If the program runs low on memory, the cached big object with ID 101 may be reclaimed. The while loop inside the program is trying to create many big objects to make the program run low on memory. The programmer is expecting that when the program is executed, it should reclaim memory used by the big object with ID 101, before throwing an OutOfMemoryError.
The output shows that the program did not reclaim the memory used by the big object with ID 101. Why did the garbage collector not behave the way it was expected to behave? There is an error in the code for the WrongSoftRef class. The big object with ID 101 is strongly reachable because the bigObj reference to it is a strong reference. You must set the bigObj reference variable to null to make it softly reachable.
Listing 11-8 shows the correct use of soft references. It is clear from the output that the finalize() method of the big object with ID 101 was called and the object was reclaimed before JVM threw an OutOfMemoryError. You still got an OutOfMemoryError because you are creating many new objects inside a while loop and all of them are strongly reachable from the array list. This proves the point that soft references are cleared and the referents are reclaimed by the garbage collector before JVM throws an OutOfMemoryError. You may get a different output. Sometimes, you get an OutOfMemoryError without the object being reclaimed.
// CorrectSoftRef.java
package com.jdojo.gc;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
public class CorrectSoftRef {
    public static void main(String[] args) {
        // Create a big object with an id 101 for caching
        BigObject bigObj = new BigObject(101);
        // Wrap soft reference inside a soft reference
        SoftReference<BigObject> sr = new SoftReference<>(bigObj);
        // Set bigObj to null, so the big object will be
        // softly reachable and can be reclaimed, if necessary.
        bigObj = null;
        // Let us try to create many big objects storing their
        // references in an array list, just to use up big memory.
        ArrayList<BigObject> bigList = new ArrayList<>();
        long counter = 102;
        while (true) {
            bigList.add(new BigObject(counter++));
        }
    }
}
finalize() called for id: 101
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
Listing 11-8.
A Correct Use of a Soft Reference
Listing 11-9 illustrates how to use soft references to implement memory-sensitive caches.
// BigObjectCache.java
package com.jdojo.gc;
import java.lang.ref.SoftReference;
public class BigObjectCache {
    @SuppressWarnings("unchecked")
    private static final SoftReference<BigObject>[] cache = new SoftReference[10];
    public static BigObject getObjectById(int id) {
        // Check for valid cache id
        if (id < 0 || id >= cache.length) {
            throw new IllegalArgumentException("Invalid id");
        }
        BigObject obj;
        // Check if we have a cache for this id
        if (cache[id] == null) {
            // We have not cached the object yet. Cache and return it.
            obj = createCacheForId(id);
            return obj;
        }
        // Get the BigObject reference using a soft reference
        obj = cache[id].get();
        // Make sure the object has not yet been reclaimed
        if (obj == null) {
            // Garbage collector has reclaimed the object.
            // Cache it again and return the newly cached object.
            obj = createCacheForId(id);
        }
        return obj;
    }
    // Creates cache for a given id
    private static BigObject createCacheForId(int id) {
        BigObject obj = null;
        if (id >= 0 && id < cache.length) {
            obj = new BigObject(id);
            cache[id] = new SoftReference<>(obj);
        }
        return obj;
    }
}
Listing 11-9.
Creating a Cache Using Soft References
It can cache up to 10 objects of the BigObject class with IDs from 0 to 9. To get the cached object for a given ID, you need to call the getObjectById() method. If that ID has not yet been cached or it was reclaimed by the garbage collector, the method creates and caches the object. This example is very restrictive and its purpose is only to demonstrate the use of the SoftReference class to maintain a memory-sensitive cache. You can cache only objects with IDs from 0 to 9. It can be modified to meet specific requirements. For example, you can use an ArrayList to cache the objects instead of using an array. You can use the BigObjectCache class as shown:
// Get the object from cache
BigObject cachedObject = BigObjectCache.getObjectById(5);
/* Do some processing...*/
// You must set the cachedObject to null after you are done with it, so the cached object
// becomes softly reachable and may be reclaimed by the garbage collector.
cachedObject = null;
If an object with an ID of 5 is not already in the cache, it will be cached and the new object reference will be assigned to cachedObject. If an object with an ID of 5 is already in the cache, the reference of that object from the cache will be returned and assigned to cachedObject.

Using the ReferenceQueue Class

An object of the ReferenceQueue<T> class is used in conjunction with objects of the SoftReference<T>, WeakReference<T>, and PhantomReference<T> classes if the object needs to be notified when its reachability changes. An object of any of these reference classes can be registered with a reference queue, as shown:
ReferenceQueue<BigObject> q = new ReferenceQueue<>();
SoftReference<BigObject> sr = new SoftReference<>(new BigObject(19), q);
WeakReference<BigObject> wr = new WeakReference<>(new BigObject(20), q);
PhantomReference<BigObject> pr = new PhantomReference<>(new BigObject(21), q);
It is optional to register the SoftReference and WeakReference objects with a reference queue. However, you must register a PhantomReference object with a reference queue. When a SoftReference or WeakReference is cleared by the garbage collector, the reference of the SoftReference or the WeakReference object is appended to the reference queue. Note the references of the SoftReference and WeakReference are placed in the queue, not the reference of their referent. For example, if the garbage collector clears the soft reference to a BigObject with ID 19 in the previous snippet of code, sr will be placed in the reference queue. In case of a PhantomReference, when its referent becomes phantom reachable, the garbage collector places the PhantomReference object in the reference queue.
Until JDK9, unlike soft and weak references, the garbage collector did not clear the phantom references as it placed them in their reference queue. The program must clear the phantom references by calling the clear() method. From JDK9, all three types of references are cleared before they are enqueued.
There are two ways to determine if a reference object has been placed in its reference queue. You can call the poll() or remove() method on a ReferenceQueue, or you can call the isEnqueued() method on the soft, weak, and phantom references. The poll() method removes a reference from the queue and returns the reference. If there is no reference available in the queue, it returns null. The remove() method works the same as the poll() method, except that if there is no reference available in the queue, it blocks until a reference becomes available. The isEnqueued() method for soft, weak, and phantom references returns true if they are placed in queue. Otherwise, it returns false. Listing 11-10 demonstrates how to use the ReferenceQueue class.
// ReferenceQueueDemo.java
package com.jdojo.gc;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class ReferenceQueueDemo {
    public static void main(String[] args) {
        // Create a reference queue
        ReferenceQueue<BigObject> q = new ReferenceQueue<>();
        // Wrap a BigObject inside a soft reference.
        // Also register the soft reference with the reference queue
        BigObject bigObj = new BigObject(131);
        WeakReference<BigObject> wr = new WeakReference<>(bigObj, q);
        // Clear the strong reference to the big object
        bigObj = null;
        // Check if weak reference has been queued
        System.out.println("Before calling gc():");
        printMessage(wr, q);
        // Invoke garbage collector. If it runs, it will clear the weak reference
        System.out.println("Invoking garbage collector...");
        System.gc();
        System.out.println("Garbage collector finished...");
        // Check if weak reference has been queued
        System.out.println("After calling gc():");
        printMessage(wr, q);
    }
    public static void printMessage(WeakReference<BigObject> wr,
            ReferenceQueue<BigObject> q) {
        System.out.println("wr.get() = " + wr.get());
        System.out.println("wr.isEnqueued() = " + wr.isEnqueued());
        WeakReference<BigObject> temp = (WeakReference<BigObject>) q.poll();
        if (temp == wr) {
            System.out.println("q.poll() returned wr");
        } else {
            System.out.println("q.poll() = " + temp);
        }
    }
}
Before calling gc():
wr.get()= BigObject: id = 131
wr.isEnqueued()= false
q.poll()= null
Invoking garbage collector...
Garbage collector finished...
After calling gc():
wr.get()= null
wr.isEnqueued()= true
q.poll() returned wr
finalize() called for id: 131
Listing 11-10.
Using the ReferenceQueue Class

Using the WeakReference Class

The only difference between a softly reachable and a weakly reachable object is that the garbage collector clears and reclaims weakly reachable objects whenever it runs, whereas it uses some algorithm to decide whether it needs to clear and reclaim a softly reachable object or not. In other words, the garbage collector may or may not reclaim a softly reachable object, whereas it always reclaims a weakly reachable object.
You may not see any important use of a weak reference because its referent is reclaimed when the garbage collector is run. Generally, weak references are not used to maintain caches. They are used to associate extra data with an object. Suppose you have a person’s details and his address. If you lose his details, you will not be interested in his address. However, as long as the person’s details are accessible, you want to keep his address information. This kind of information can be stored using weak references and a Hashtable. A Hashtable stores objects in key-value pairs. While adding a key-value pair to a Hashtable, you need to wrap the key object in a WeakReference object. The key and value are not garbage collected when the key is accessible or in use. When the key object is no longer in use, it will be garbage collected because it was wrapped inside a WeakReference. At that point, you can remove that entry from the Hashtable, so the value object will also be eligible for the garbage collection. The following is a sample snippet of code using Hashtable and WeakReference objects:
// Create a Hashtable object
Hashtable ht = new Hashtable();
// Create a reference queue, so  we can check when a key was garbage collected
Referencequeue q = new ReferenceQueue();
// Create key and value objects
key = your key object creation logic goes here
value = your value object creation logic goes here
// Create a weak reference object using the key object as the referent
WeakReference wKey = new WeakReference(key, q);
// Place the key-value pair in the Hashtable. Note that we place key wrapped
// in the weak reference. That is, we will use wKey as key
ht.put(wKey, value);
/* Use key and value objects in your program... */
// When done with the key object, set it to null, so it will not be strongly reachable.
key = null;
/* At this point, if garbage collector is run, weak reference to key object will be cleared
   and the WeakReference, wr, will be placed in reference queue, q.
*/
// Your logic to remove the entry for garbage collected key object will be as follows
if (wr.isEnqueued()) {
    // This will make value object eligible for reclamation
    ht.remove(wr);
}
Note that using a WeakReference object to associate extra information with an object using a Hashtable involves some complex code and logic. The java.util.WeakHashMap class provides this functionality without writing any complex logic. You add the key-value pairs to a WeakHashMap without wrapping the key object inside a WeakReference. The WeakHashMap class takes care of creating a reference queue and wrapping the key object in a WeakReference. There is one important point to remember while using a WeakHashMap. The key object is reclaimed when it is not strongly reachable. However, the value object is not reclaimed immediately. The value object is reclaimed after the entry is removed from the map. The WeakHashMap removes the entry after the weak reference to the key has been cleared and one of its methods—put(), remove(), or clear()—is called. Listing 11-11 demonstrates the use of a WeakHashMap. The example uses objects of the BigObject class as keys as well as values. The messages in the output show when the key and value objects are reclaimed by the garbage collector. You may get different output when you run this program.
// WeakHashMapDemo.java
package com.jdojo.gc;
import java.util.WeakHashMap;
public class WeakHashMapDemo {
    public static void main(String[] args) {
        // Create a WeakHashMap
        WeakHashMap<BigObject, BigObject> wmap = new WeakHashMap<>();
        // Add two key-value pairs to WeakHashMap
        BigObject key1 = new BigObject(10);
        BigObject value1 = new BigObject(110);
        BigObject key2 = new BigObject(20);
        BigObject value2 = new BigObject(210);
        wmap.put(key1, value1);
        wmap.put(key2, value2);
        // Print a message
        printMessage("After adding two entries:", wmap);
        /* Invoke gc(). This gc() invocation will not reclaim any of
           the key objects, because we are still having their strong references.
         */
        System.out.println("Invoking gc() first time...");
        System.gc();
        // Print a message
        printMessage("After first gc() call:", wmap);
        // Now remove strong references to keys and values
        key1 = null;
        key2 = null;
        value1 = null;
        value2 = null;
        /* Invoke gc(). This gc() invocation will reclaim two key objects
           with ids 10 and 20. However, the corresponding two value objects
           will still /be strongly referenced by WeakHashMap internally and hence
           will not be reclaimed at this point.
         */
        System.out.println("Invoking gc() second time...");
        System.gc();
        // Print a message
        printMessage("After second gc() call:", wmap);
        /* Both keys have been reclaimed by now. Just to make value
           objects reclaimable, we will call clear() method on WeakHashMap.
           Usually, you will not call this method here in your program.
         */
        wmap.clear();
        // Invoke gc() so that value object will be reclaimed
        System.out.println("Invoking gc() third time...");
        System.gc();
        // Print message
        printMessage("After calling clear() method:", wmap);
    }
    public static void printMessage(String msgHeader, WeakHashMap wmap) {
        System.out.println(msgHeader);
        // Print the size and content of map */
        System.out.println("Size = " + wmap.size());
        System.out.println("Content = " + wmap);
        System.out.println();
    }
}
After adding two entries:
Size = 2
Content = {BigObject: id = 20=BigObject: id = 210, BigObject: id = 10=BigObject: id = 110}
Invoking gc() first time...
After first gc() call:
Size = 2
Content = {BigObject: id = 20=BigObject: id = 210, BigObject: id = 10=BigObject: id = 110}
Invoking gc() second time...
After second gc() call:
finalize() called for id: 20
finalize() called for id: 10
Size = 0
Content = {}
Invoking gc() third time...
finalize() called for id: 210
finalize() called for id: 110
After calling clear() method:
Size = 0
Content = {}
Listing 11-11.
Using a WeakHashMap

Using the PhantomReference Class

A PhantomReference object must be created with a ReferenceQueue. When the garbage collector determines that there are only phantom references to an object, it finalizes the object and adds the phantom references to their reference queues.
Until JDK8, phantom references worked a little differently than soft and weak references. Unlike soft and weak references, it did not clear the phantom references to the object automatically. Programs must clear it by calling the clear() method. A garbage collector would not reclaim the object until the program clears the phantom references to that object. Therefore, a phantom reference acted as a strong reference as long as reclaiming of objects is concerned. This behavior has changed in JDK9. In JDK9, phantom references automatically clear references as soft and weak references do.
Why would you use a phantom reference instead of using a strong reference? A phantom reference is used to do post-mortem processing. Unlike the get() method of the soft and weak references, the phantom reference’s get() method always returns null. An object is phantom reachable when it has been finalized. If a phantom reference returns the referent’s reference from its get() method, it would resurrect the referent. This is why the phantom reference’s get() method always returns null.
Listing 11-12 demonstrates the use of a phantom reference to do some post-mortem processing for an object. You may get different output when you run this program.
// PhantomRef.java
package com.jdojo.gc;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomRef {
    public static void main(String[] args) {
        BigObject bigObject = new BigObject(1857);
        ReferenceQueue<BigObject> q = new ReferenceQueue<>();
        PhantomReference<BigObject> pr = new PhantomReference<>(bigObject, q);
        /* You can use BigObject reference here */
        // Set BigObject to null, so garbage collector will find only the
        // phantom reference to it and finalize it.
        bigObject = null;
        // Invoke garbage collector
        printMessage(pr, "Invoking gc() first time:");
        System.gc();
        printMessage(pr, "After invoking gc() first time:");
        // Invoke garbage collector again
        printMessage(pr, "Invoking gc() second time:");
        System.gc();
        printMessage(pr, "After invoking gc() second time:");
    }
    public static void printMessage(PhantomReference<BigObject> pr, String msg) {
        System.out.println(msg);
        System.out.println("pr.isEnqueued = " + pr.isEnqueued());
        System.out.println("pr.get() = " + pr.get());
        // We will check if pr is queued. If it has been queued,
        // we will clear its referent's reference.
        if (pr.isEnqueued()) {
            // Calling pr.clear() was necessary before JDK9.
            // From JDK9, phantom references are clear automatically
            pr.clear();
            System.out.println("Cleared the referent's reference");
        }
        System.out.println("-----------------------");
    }
}
Invoking gc() first time:
pr.isEnqueued = false
pr.get() = null
-----------------------
finalize() called for id: 1857
After invoking gc() first time:
pr.isEnqueued = false
pr.get() = null
-----------------------
Invoking gc() second time:
pr.isEnqueued = false
pr.get() = null
-----------------------
After invoking gc() second time:
pr.isEnqueued = true
pr.get() = null
Cleared the referent's reference
-----------------------
Listing 11-12.
Using PhantomReference Objects
You can also use phantom references to coordinate the post-mortem processing of more than one object. Suppose you have three objects called obj1, obj2, and obj3. All of them share a network connection. When all three objects become unreachable, you would like to close the shared network connection. You can achieve this by wrapping the three objects in a phantom reference object and using a reference queue. Your program can wait on a separate thread for all three phantom reference objects to be queued. When the last phantom reference is queued, you can close the shared network connection. Post-mortem coordination using a phantom reference is demonstrated in Listing 11-13. Note that the startThread() method of the PhantomRefDemo class creates and starts a thread that waits for three references to be enqueued. Once all three references are enqueued and their referents clears, the thread exits the application. The remove() method of the ReferenceQueue class blocks until there is a phantom reference in the queue. You may get different output when you run this program.
// PhantomRefDemo.java
package com.jdojo.gc;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class PhantomRefDemo {
    public static void main(String[] args) {
        final ReferenceQueue<BigObject> q = new ReferenceQueue<>();
        BigObject bigObject1 = new BigObject(101);
        BigObject bigObject2 = new BigObject(102);
        BigObject bigObject3 = new BigObject(103);
        PhantomReference<BigObject> pr1 = new PhantomReference<>(bigObject1, q);
        PhantomReference<BigObject> pr2 = new PhantomReference<>(bigObject2, q);
        PhantomReference<BigObject> pr3 = new PhantomReference<>(bigObject3, q);
        /* This method will start a thread that will wait for the arrival of new
           phantom references in reference queue q
         */
        startThread(q);
        /* You can use bigObject1, bigObject2 and bigObject3 here */
        // Set the bigObject1, bigObject2 and bigObject3 to null,
        // so the objects they are referring to may become phantom reachable.
        bigObject1 = null;
        bigObject2 = null;
        bigObject3 = null;
        /* Let us invoke garbage collection in a loop. One garbage collection will
           just finalize the three big objects with IDs 101, 102 and 103. They may
           not be placed in a reference queue. In another garbage collection run,
           they will become phantom reachable and they will be placed in a queue
           and the waiting thread will remove them from the queue and will clear
           their referent's reference. Note that we exit the application when all
           three objects are cleared inside the run() method of thread. Therefore, the
           following infinite loop is ok for demonstration purposes. If System.gc()
           does not invoke the garbage collector on your machine, you should replace
           the following loop with a loop which would create many big objects keeping
           their references, so the garbage collector would run.
         */
        while (true) {
            System.gc();
        }
    }
    public static void startThread(final ReferenceQueue<BigObject> q) {        
        Thread t = new Thread(() -> {
            try {
                // Wait and clear 3 references
                for(int i = 0; i < 3; i++) {
                    Reference r = q.remove();    
                    // Calling r.clear() was necessary before JDK9.
                    // From JDK9, it has no effect.
                    r.clear();
                }
                System.out.println("All three objects have been queued and cleared.");
                /* Typically, you will release the network connection or
                   any resources shared by three objects here.
                */
                // Exit the application
                System.exit(1);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        });
        // Start the thread, which will wait for three phantom references to be queued
        t.start();
    }
}
finalize() called for id: 103
finalize() called for id: 102
finalize() called for id: 101
All three objects have been queued and cleared.
Listing 11-13.
Post-Finalization Coordination Using Phantom References

Using the Cleaner Class

In the previous sections, you learned how to use PhantomReference and ReferenceQueue to perform cleanup work for objects when they become phantom reachable. To set up and perform the cleanup work was not easy. JDK9 introduced a new class named Cleaner in the java.lang.ref package. Its use to let you run a cleanup action for an object when the object becomes phantom reachable. The Cleaner class is intended to make setting up and performing the cleanup work easier for you. Here are the steps you need to perform:
  1. 1.
    Create a Cleaner instance using one of its factory methods named create()… You can let the Cleaner use predefined threads to perform the cleanup actions or you can specify your own ThreadFactory in the create() method.
     
  2. 2.
    Register the object and its cleaning action using the register() method of the Cleaner. A cleaning action is a Runnable.
     
  3. 3.
    The register() method of the Cleaner class returns an instance of the Cleaner.Cleanable nested interface. The interface contains only one method named clean().
     
  4. 4.
    Call the clean() method of a Cleanable to unregister the object and perform the cleanup work. Performing the cleanup work is simply calling the run() method of the registered Runnable. Calling the clean() method the second time has no effect because the first call to this method unregisters the object .
     
  5. 5.
    Typically, the clean() method of a Cleanable is called by one of the threads in the Cleaner. However, if you know the time and place when the cleanup needs to happen, you can perform the cleanup explicitly by calling this method.
     
  6. 6.
    If you intend to use the objects of your class inside try-with-resources blocks, you need to implement the AutoCloseable interface. You can call the clean() method of the Cleanable representing your registered object from inside the close() method.
     
Now I walk you through an example on how to use the Cleaner class. You can use one of the following methods of the Cleaner class to create a Cleaner :
  • static Cleaner create()
  • static Cleaner create(ThreadFactory threadFactory)
Typically, you would create a Cleaner object for the entire application or library and store its reference in a static variable. The following statement creates a Cleaner:
Cleaner cleaner = Cleaner.create();
Suppose you have the following object that needs cleanup work when it becomes phantom reachable:
Object myObject = /* get your object */;
The next step is to define a cleaning action, which is a Runnable. There are several ways to create a Runnable such as using a lambda expression, inner class, anonymous inner class, nested inner class, and having a top-level class, which implements the Runnable interface. It does not matter which method you choose to create a Runnable. It is important to make sure that the Runnable does not store the reference of the object whose cleanup work it is supposed to perform. Otherwise, the object will never become phantom reachable and your cleaning action will never be called by the Cleaner. You need to make all resources that need to be cleaned up are accessible to the Runnable. Suppose you have an object that stores a network connection and you want to close the connection as part of the object cleanup. You will need to make the network connection accessible to the Runnable, so it can close the connection when the cleanup work is performed. The following pseudo statement creates a Runnable:
Runnable cleaningAction = /* get a Runnable instance */;
The following statement registers myObject and its cleaningAction with the Cleaner:
Cleaner.Cleanable cleanable = cleaner.register(myObject, cleaningAction);
Typically, you will keep the reference of the Cleanable in an instance variable of your object, so you can call its clean() method directly to clean up your object explicitly, if needed. Listing 11-14 contains the code for a CleanBigObject class. Explanation of its parts follows the code .
// CleanBigObject.java
package com.jdojo.gc;
import java.lang.ref.Cleaner;
public class CleanBigObject implements AutoCloseable {
    // Declare a big array of 20KB.
    private final long[] anArray = new long[20480];
    // Have an id to track the object
    private final long id;
    // Let us use a Cleaner
    public static Cleaner cleaner = Cleaner.create();
    // Keep a reference of its cleaning action as a Cleanable
    private final Cleaner.Cleanable cleanable;
    // Declare a cleaning action class, which needs to implement Runnable
    private static class BigObjectCleaner implements Runnable {
        private final long id;
        BigObjectCleaner(long id) {
            this.id = id;            
        }
        @Override
        public void run() {
            System.out.println("Cleaning up CleanBigObject: id = " + this.id);
        }    
    }
    public CleanBigObject(long id) {
        this.id = id;
        // Register this object with the cleaner
        this.cleanable = cleaner.register(this, new BigObjectCleaner(id));
    }
    @Override
    public void close() {
        // Clean the object explicitly or as part of a try-with-resources block
        cleanable.clean();
    }
     @Override
    public String toString() {
        return "CleanBigObject: id = " + id;
    }
}
Listing 11-14.
The CleanBigObject Class
Here are the different parts of the CleanBigObject class :
  • The CleanBigObject class declares a big long array.
  • Its id instance variable tracks the ID of each object.
  • It creates and stores a Cleaner object in a public class variable. This Cleaner is supposed to be used by the object of this class and other classes to register cleaning actions.
  • It declares a private instance variable of type Cleaner.Cleanable, which stores the registered cleaning action for later use such as in its close() method.
  • The BigObjectCleaner class is a private nested static class, which implements Runnable; its instances represents a cleaning action for the object of the CleanBigObject class. The constructor of the class accepts the ID of the CleanBigObject. In a real-word application, the constructer would accept the resources to be cleaned. The run() method simply prints a message with the ID of the CleanBigObject that is being cleaned up.
  • The constructor of the CleanBigObject class accepts an ID to identify the object. The ID is stored in its instance variable. The constructor registers the object and its cleaning action with the Cleaner.
  • The close() method of the CleanBigObject class has been implemented because the class implements the AutoCloseable interface, so you can use the objects of this class in try-with-resources blocks. The method calls the clean() method of the Cleanable that will clean up the CleanBigObject if it has not already been cleaned.
  • The toString() method returns a string representation of the object with its ID.
Listing 11-15 contains the code for a CleanerTest class. In its main() method, it creates three objects of the CleanBigObject class and tries to clean up those objects in three different ways. The first example uses a try-with-resources block, so the close() method of the CleanBigObject class is automatically called, which cleans up the object. The second example cleans up the object explicitly by calling its close() method. The third example creates the object without storing its reference and invokes the garbage collection by calling System.gc(). In the end, the program sleeps for two seconds to give the garbage collection time to finish if the previous call to System.gc() makes the JVM run the garbage collection. Note that there is no guarantee that garbage collection will run and, in that case, you may not see the last line in the output.
// CleanerTest.java
package com.jdojo.gc;
public class CleanerTest {
    public static void main(String[] args) throws InterruptedException {
        // Let us try a CleanBigObject in a try-with-resources block        
        try (CleanBigObject cbo1 = new CleanBigObject(1969);) {
            System.out.println(cbo1 + " created inside a try-with-resources block.");
        }
        // Let us create and clean a CleanBigObject explicitly
        CleanBigObject cbo2 = new CleanBigObject(1968);
        System.out.println(cbo2 + " created.");
        cbo2.close();
        cbo2 = null;
        // Let us create many CleanBigObject and let the Cleaner
        // clean those objects automatically
        new CleanBigObject(1982);
        System.gc();
        // Wait for 2 seconds for the garbage collector to finish
        Thread.sleep(20000);
    }
}
CleanBigObject: id = 1969 created inside a try-with-resources block.
Cleaning up CleanBigObject: id = 1969
CleanBigObject: id = 1968 created.
Cleaning up CleanBigObject: id = 1968
Cleaning up CleanBigObject: id = 1982
Listing 11-15.
A Test Class to Test the Objects of the CleanBigObject Class

Summary

The process of reclaiming the memory of dead objects is known as garbage collection. Garbage collection in Java is automatic. The Java runtime runs garbage collection in a low priority background thread. The JVM does its best to free up memory of dead objects before throwing an OutOfMemoryError. You can pass a hint, although it’s not needed in an application, to the JVM by calling Runtime.getRuntime().gc(). You can also use the convenience method System.gc() to pass the same hint to the JVM. The JVM is free to ignore the hint.
The memory occupied by an unreachable object is reclaimed in two phases. The first phase, called finalization, is an action automatically performed on an unreachable object before the memory used by the object is reclaimed by the garbage collector. The block of code that contains the action to be performed is known as a finalizer. A finalizer is implemented using the finalize() method of the object. In the finalize() method, the unreachable object may resurrect itself by storing its reference in a reachable object. In the second phase, if the object is still unreachable, the memory occupied by the object is reclaimed.
At times, you may want to use memory-sensitive objects, which are fine to be kept in memory if enough memory is available. However, if the application runs low on memory, it would be fine to reclaim those objects. Typically, objects cached for a better performance fall into this category of objects. Java provides the SoftReference<T>, WeakReference<T>, and PhantomReference<T> classes in the java.lang.ref package to work with such memory-sensitive objects. These objects may be queued to a ReferenceQueue when their referent’s reachability changes, so you can inspect the queue and perform cleanup work.
JDK9 added a new class named Cleaner to the java.lang.ref package. This class offers a better way to clean up objects when the objects become phantom reachable. The Cleaner lets you register objects and their cleaning actions as a Runnable. When a registered object becomes phantom reachable, the Cleaner performs the cleanup work using the registered Runnable for that object. The Cleaner also allows you to clean up the object explicitly. It guarantees that the cleanup will be performed only once in any case.
Questions and Exercises
  1. 1.
    What is the difference between explicit and implicit memory allocation and memory reclamation?
     
  2. 2.
    What is a dangling reference?
     
  3. 3.
    What is memory leak?
     
  4. 4.
    What is garbage collection and a garbage collector?
     
  5. 5.
    Show two ways to call the garbage collector in your program.
     
  6. 6.
    What is the finalize() method? How is it used by the garbage collector?
     
  7. 7.
    What does the Java runtime do before throwing an OutOfMemoryError?
     
  8. 8.
    What is an object resurrection in Java?
     
  9. 9.
    How do you request that the Java runtime run garbage collection?
     
  10. 10.
    Describe the uses of the WeakReference<T>, SoftReference<T>, and PhantomReference<T> classes.
     
  11. 11.
    When do you use a ReferenceQueue?
     
  12. 12.
    How is the Cleaner class, which was introduced in JDK9, used?
     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.171.181