[2.4] Explain an Object’s Lifecycle (creation, “dereference by reassignment” and garbage collection)
The OCA Java SE 8 Programmer I exam will test your understanding of when an object is created, when it can be accessed, and when it can be dereferenced. The exam also tests your ability to determine the total number of objects that are accessible at a particular line of code. Primitives aren’t objects, so they’re not relevant in this section.
Unlike some other programming languages, such as C, Java doesn’t allow you to allocate or deallocate memory yourself when you create or destroy objects. Java manages the memory for allocating objects and reclaiming the memory occupied by unused objects.
The task of reclaiming unused memory is taken care of by Java’s garbage collector, which is a low-priority thread. It runs periodically and frees up space occupied by unused objects.
Java also provides a method called finalize, which is accessible to all the classes. The method finalize is defined in the class java.lang.Object, which is the base class of all Java classes. All Java classes can override the method finalize, which executes just before an object is garbage collected. In theory, you can use this method to free up resources being used by an object, although doing so isn’t recommended because its execution isn’t guaranteed to happen.
An object’s life cycle starts when it’s created and lasts until it goes out of scope or is no longer referenced by a variable. When an object is accessible, it can be referenced by a variable and other classes can use it by calling its methods and accessing its variables. I’ll discuss these stages in detail in the following subsections.
An object comes into the picture when you use the keyword operator new. You can initialize a reference variable with this object. Note the difference between declaring a variable and initializing it. The following is an example of a class Person and a class ObjectLifeCycle:
In the preceding code, no objects of class Person are created in the class ObjectLifeCycle; it declares only a variable of type Person. An object is created when a reference variable is initialized:
The difference in variable declaration and object creation is illustrated in figure 3.9, where you can compare a baby name to a reference variable and a real baby to an object. The left box in figure 3.9 represents variable declaration, because the baby hasn’t been born yet. The right box in figure 3.9 represents object creation.
Syntactically, an object comes into being by using the new operator. But the String class is an exceptional case here. String reference variables can also be initialized by using string literal values:
Initializing a reference variable and an instance is not same. Initializing a reference variable might not always result in the creation of a new instance. In chapter 4, we’ll cover in detail how String literal values are pooled in a String pool by JVM. Although using the new operator always creates a new String object, using a String literal value to initialize a String reference variable might not always create a new String object.
What happens when you create a new object without assigning it to any reference variable? Let’s create a new object of class Person in class ObjectLifeCycle2 without assigning it to any reference variable (modifications in bold):
In the preceding example, an object of the class Person is created, but it can’t be accessed using any reference variable. Creating an object in this manner will execute the relevant constructors of the class.
Watch out for a count of instances created in a given code—the ones that are eligible for garbage collection and the ones that aren’t.
In the next section, you’ll learn what happens after an object is created.
Once an object is created, it can be accessed using its reference variable. It remains accessible until it goes out of scope or its reference variable is explicitly set to null. Also, if you reassign another object to an initialized reference variable, the previous object becomes inaccessible from that variable. You can access and use an object within other classes and methods.
Look at the following definition of the class Exam:
class Exam { String name; public void setName(String newName) { name = newName; } }
The class ObjectLife1 declares a variable of type Exam, creates its object, calls its method, sets it to null, and then reinitializes it:
The preceding example creates two objects of the class Exam using the same reference variable myExam. Let’s walk through what’s happening in the example:
When creates another object of class Exam and assigns it to the variable myExam, what happens to the first object created by ? Because the first object can no longer be accessed using any variable, it’s considered garbage by Java and deemed eligible to be sent to the garbage bin by Java’s garbage collector. As mentioned earlier, the garbage collector is a low-priority thread that reclaims the space used by unused or unreferenced objects in Java.
What happens when an object become inaccessible? You’ll find out in the next section.
An object can become inaccessible if it goes out of scope or is dereferenced by reassignment.
An object can become inaccessible if it goes out of scope:
In the preceding code, the variable myExam1 is a local variable defined within the if block. Its scope starts from the line where it’s declared until the end of the if block, marked with a closing brace . After this closing brace, the object referred by the variable myExam1 is no longer accessible. It goes out of scope and is marked as eligible for garbage collection by Java’s garbage collector. Similarly, the object referred to by the variable myExam2 becomes inaccessible at the end of the else block, marked with a closing brace .
When an object goes out of scope, it can no longer be referenced and is marked for garbage collection.
A variable that already refers to an instance can be assigned another instance. In this case, the earlier instance is dereferenced and becomes eligible for garbage collection. Let’s work with a modified version of a previous code example:
In the preceding code, an Exam instance is created and assigned to the variable myExam . At myExam is set to null before being assigned another Exam instance . The code at reassigns yet another Exam instance to myExam, without explicitly setting it to null. Again, the instance created at is dereferenced. After the execution of , two MyExam instances are dereferenced by reassignment and are eligible for garbage collection.
At , another variable, yourExam, is initialized using an Exam instance. At , the variable myExam is assigned to the variable yourExam. This dereferences the Exam instance, which was assigned to yourExam earlier.
Figure 3.10 shows how Exam instances are referred to by the variables myExam and yourExam. The Exam instances highlighted using gray boxes represent unreferenced objects.
An instance is dereferenced by reassignment when a variable is either explicitly set to null or is assigned another instance or reference variable.
In the OCA Java SE 8 Programmer I exam, you’re likely to answer questions on garbage collection for code that has multiple variable declarations and initializations. The exam may query you on the total number of objects that are eligible for garbage collection after a particular line of code.
The garbage collector is a low-priority thread that marks the objects eligible for garbage collection in the JVM and then clears the memory of these objects. It enables automatic memory management because programmers aren’t required to mark these instances themselves.
You can determine only which objects are eligible to be garbage collected. You can never determine when a particular object will be garbage collected. A user can’t control or determine the execution of a garbage collector. It’s controlled by the JVM.
Watch out for questions with wordings such as “which objects are sure to be collected during the next GC cycle,” for which the real answer can never be known.
Let’s revisit the dog and leash analogy I used in chapter 2 to define object reference variables. In figure 3.11, you can compare an object reference variable with a leash and an object with a dog. Review the following comparisons, which will help you to understand the life cycle of an object and garbage collection:
You can compare Java’s garbage collector to animal control. The way animal control picks up untethered dogs is like the way Java’s garbage collector reclaims the memory used by unreferenced objects.
As a programmer, you can’t start execution of Java’s garbage collector. You can only request it to be started by calling System.gc() or Runtime.getRuntime().gc(). But calling this method doesn’t guarantee when the garbage collector would start (the call can even be ignored by the JVM). Watch out for exam questions that query you on the number of instances that have been garbage collected after calling System.gc(). It won’t guarantee any count, at any line of code.
The garbage collector can also reclaim memory from a group of referenced objects. This group of variables is referred to as an island of isolation.
An instance can be referred to by multiple variables. So when you assign null to one of these variables, the instances can still be referenced using other variable(s). But a group of instances with no external reference becomes eligible for garbage collection. Let’s work with an example:
In the preceding example, an Exam instance can refer to an object of its own type, using its field other. At and , two variables, php and java, are created and initialized using Exam instances. At , java is assigned to php.other. At , php is assigned to java.other. At , when php is set to null, the instance referred to by it isn’t eligible for garbage collection because it can still be referenced using java.other. At , when java is also set to null, both the objects referred to by java and php become eligible for garbage collection. As shown in figure 3.12, even though both these objects can be referred to by each other, they can no longer be referenced in the method main. They form an island of isolation. Java’s garbage collector can determine such groups of instances.
Now that you’re familiar with an object’s life cycle, you can create methods that accept primitive data types and objects as method arguments; these methods return a value, which can be either a primitive data type or an object.
18.118.20.68