Study guide: Trash talk, Part 1

Brush up on Java terms, learn tips and cautions, review homework assignments, and read Jeff's answers to student questions

Glossary of terms

finalization
A fallback mechanism for cleaning up finite resources. Finalization occurs when the garbage collector calls a resurrectable object's overridden finalize() method.
garbage collection
The process by which some executable, such as the JVM, automatically frees an object's memory when a single reference to that memory no longer exists.
garbage collector
That part of the JVM that performs garbage collection.
handles
Indexes into a table of object references.
mark-and-sweep algorithm
A tracing-based garbage collector algorithm that first marks in some way each object during a trace through all objects on the object heap and then sweeps all unmarked objects into oblivion.
object heap
That portion of the JVM's memory area from which the JVM allocates memory for new objects.
reachable
A state in which an object can be reached via some path that starts from a root-set variable.
resurrection
The act of preventing an object's collection by making it reachable from inside its finalize() method.
root set of references
A group of reference variables always accessible to an executing Java program. Those variables include local variables, parameters, and class fields.
stop-and-copy algorithm
A copying-based garbage collector algorithm that divides the object heap into an object area and a free space area. The stop-and-copy garbage collector stops program execution while it copies objects from the object area to the free space area, defragmenting the heap in the process.
Train algorithm
A modified generational collector algorithm that manages the mature object space to shorten the delay that the garbage collector imposes on a program.
unreachable
A state in which an object cannot be reached by any path starting from any root-set variable.

Tips and cautions

These tips and cautions will help you write better programs and save you from agonizing over error messages produced by the compiler.

Tips

  • Get into the habit of ending your finalize() methods with super.finalize(); method calls. That way, if you insert a superclass above your class and if that superclass has its own finalize() method, you can be sure the garbage collector calls superclass's finalize() method.

Cautions

  • You should call System.runFinalization (); immediately after a call to System.gc ();. If you place System.runFinalization (); before System.gc (); or omit the System.gc (); call, not all finalize() methods will run. (I do not know the reason for this.)
  • If you resurrect an object and then make that object unreachable, the next time the garbage collector runs, it will collect that object without calling the object's finalize() method.

Miscellaneous notes and thoughts

You might be wondering why I did not include instance (that is, object) fields in the root set of references. The reason is that, unlike class fields (which are always accessible through a program's lifetime) and local variables/parameters (which are always accessible during a method call), instance fields become inaccessible to the garbage collector when an object becomes unreachable.

Homework

This month, you have three questions to answer:

  1. Why is it beneficial that Java, rather than the developer, frees objects?
  2. Write a program that demonstrates how you can run out of memory in a Java program. What happens when you run out of memory?
  3. What is a disadvantage to a generational garbage collector? (Hint: Why does the Train algorithm exist?)

Answers to last month's homework

Last month, I asked you to trace the initialization process through the Employees source code below (assume that java Employees starts the program):

Employees.java

// Employees.java
class Employee
{
   private String name;
   private double salary;
   static int count;
   static double bonus;
   Employee (String name, double salary)
   {
      this.name = name;
      this.salary = salary;
      if (this instanceof Accountant)
          this.salary += bonus;
   }
   static
   {
      // Pretend to load bonus from a database.
      bonus = 500.0;
   }
   {
      if (count > 5)
          bonus = 0.0;
      count++;
   }
   String getName ()
   {
      return name;
   }
   double getSalary ()
   {
      return salary;
   }
}
class Accountant extends Employee
{
   Accountant (String name, double salary)
   {
      super (name, salary);
   }
}
class Employees
{
   public static void main (String [] args)
   {
      String [] names =
      {
         "John Doe",
         "Jane Smith",
         "Jack Jones",
         "Bob Smyth",
         "Alice Doe",
         "Janet Jones"
      };
      double [] salaries =
      {
         40000.0,
         50000.0,
         30000.0,
         37500.0,
         52000.0,
         47000.0
      };
      for (int i = 0; i < names.length; i++)
           if (i < 3)
           {
               Employee e = new Employee (names [i], salaries [i]);
               System.out.println ("Name = " + e.getName ());
               System.out.println ("Salary = " + e.getSalary ());
           }
           else
           {
               Accountant a = new Accountant (names [i], salaries [i]);
               System.out.println ("Name = " + a.getName ());
               System.out.println ("Salary = " + a.getSalary ());
           }
   }
}

When run, Employees produces the following output:

Name = John Doe
Salary = 40000.0
Name = Jane Smith
Salary = 50000.0
Name = Jack Jones
Salary = 30000.0
Name = Bob Smyth
Salary = 38000.0
Name = Alice Doe
Salary = 52500.0
Name = Janet Jones
Salary = 47500.0

How does Employees achieve that output? The following numbered list takes you through the program's execution and itemizes all initialization steps until the last Accountant object finishes initializing:

  1. The JVM's class loader loads Employees.class into memory, and the JVM's byte code verifier verifies all byte code instructions in that class.
  2. Because class Employees declares no class fields, the JVM does not allocate memory for class fields and zeroes all bits comprising that memory.
  3. The JVM starts executing Employees's main() method. Memory is allocated on the method call stack for local variables names, salaries, i, e, and a.
  4. The main() method's byte code instructions create a names array and a salaries array. References to those arrays (whose memory exists on the JVM's object heap) assign to local variables names and salaries.
  5. The for loop statement byte code instructions begin to execute. Zero assigns to local variable i.
  6. Because i's value (0) is less than names.length's (6), for executes the if decision statement.
  7. if evaluates i < 3. Because i's value (0) is less than 3, the byte code instructions comprising Employee e = new Employee (names [i], salaries [i]); start to execute.
  8. The JVM detects the presence of class Employee in those instructions and has its class loader load Employee.class. The JVM's byte code verifier then verifies all byte code instructions comprising that class.
  9. Because class Employee declares two class fields (count and bonus), the JVM allocates memory for those class fields and zeroes all bits comprising that memory.
  10. The JVM executes Employee's <clinit> method. That method assigns 500.0 to bonus.

  11. The JVM allocates memory for an Employee object's name and salary instance fields, and zeroes all bits comprising that memory. A reference to that object's memory is placed in a temporary stack location.
  12. The JVM executes the <init> method corresponding to Employee(String name, double salary).
  13. The <init> method first calls Object's no-argument <init> method, which does nothing and subsequently returns.
  14. The <init> method next executes an if decision statement's byte code instructions that evaluate count > 5. Because count currently contains 0, 0.0 does not assign to bonus.
  15. The <init> method next executes the equivalent of count++. count now contains 1.
  16. The <init> method next assigns the reference in its name parameter to the name field and the double-precision floating-point value in its salary parameter to the salary field.
  17. Finally, the <init> method uses the byte code equivalent of instanceof to see if the current object is an instance of Accountant. Because the object is not such an instance, nothing further happens and <init> exits.
  18. The JVM takes the Employee object's reference from the temporary stack location and assigns it to local variable e.
  19. The two System.out.println() method calls result in calls to Employee's getName() and getSalary() methods for the object referenced by e. Those methods return the appropriate name and salary values, which subsequently print.
  20. The for loop statement increments i (which becomes 1), and checks to see if i's value is still less than names.length's (6) -- which it still is. Therefore, the if decision statement executes a second time.
  21. Because the next two iterations (where i contains 1 and 2) repeat previous steps, let's continue where i contains 3.
  22. if evaluates i < 3. Because i's value equals 3, the byte code instructions comprising Accountant a = new Accountant (names [i], salaries [i]); start to execute.
  23. The JVM detects the presence of class Accountant in those instructions and has its class loader load Accountant.class. The JVM's byte code verifier then verifies all byte code instructions comprising that class.
  24. Because class Accountant declares no class fields, the JVM does not allocate memory for class fields and zeroes all bits comprising that memory.
  25. Because class Accountant contains no class initializers, there is no <clinit> method to execute.
  26. The JVM allocates memory for an Accountant object's name and salary instance fields (which are located in the Employee layer), and zeroes all bits comprising that memory. A reference to the Accountant object is placed in a temporary stack location.
  27. The JVM executes the <init> method corresponding to Accountant(String name, double salary).
  28. The <init> method first calls Employee's two-argument <init> method.
  29. Employee's <init> method calls Object's no-argument <init> method, which does nothing and subsequently returns.
  30. Employee's <init> method next executes the if decision statement that evaluates count > 5. Because count currently contains 3, 0.0 does not assign to bonus.
  31. Employee's <init> method next executes the equivalent of count++. count now contains 4.
  32. Employee's <init> method next assigns the reference in its name parameter to the name field and the double-precision floating-point value in its salary parameter to the salary field.
  33. Finally, Employee's <init> method uses the byte code equivalent of instanceof to see if the current object is an instance of Accountant. Because the current object is such an instance, the contents of bonus (500.0) are added to the salary field.
  34. Employee's <init> method exits to Accountant's <init> method.
  35. Accountant's <init> method has nothing further to do, so it exits.
  36. The JVM takes the Accountant object's reference from the temporary stack location and assigns it to local variable a.
  37. The two System.out.println() method calls result in calls to Accountant's inherited getName() and getSalary() methods for the object referenced by a. Those methods return the appropriate name and salary values, which subsequently print.
  38. The for loop statement increments i (which becomes 4), and checks to see if i's value is still less than names.length's (6) -- which it still is. Therefore, the if decision statement executes a second time.
  39. Because the next two iterations (where i contains 4 and 5) repeat previous steps, let's continue where i contains 6.
  40. Now that i contains 6, the for loop statement, followed by main(), exits. Before main() exits, the JVM releases memory for the local variables from the method call stack.
Related: