Lectures: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13.

Process Memory Layout

This is the last of three self study modules dedicated to process memory layout, dealing with managed (garbage collected) heaps. The goal is to explain basic concepts related to garbage collection (especially reachability) and again give a reasonable impression of what overhead to expect when using heap with a garbage collector.

At the end of this module, you should be able to:

  • explain the role of a garbage collector,
  • distinguish garbage from live objects on sample program fragments,
  • discuss the differences in coding with garbage collector vs explicit heap management,
  • explain how reference tracing or counting is used to distinguish garbage from live objects,
  • describe the mark-and-sweep garbage collector algorithm,
  • describe the copying garbage collector algorithm,
  • state the generational hypothesis,
  • describe a generational garbage collector,
  • describe where the garbage collector algorithms above obtain input information, specifically
    • the collection roots,
    • the inter-object references,
    • the inter-generation references,
  • describe the issues associated with concurrency between the collector and the mutator,
  • discuss the overheads associated with garbage collection,
  • compare the overheads with explicit heap management.

Typically, heap implementations with garbage collection reside in runtime environments of high level programming languages (such as the Java Virtual Machine or the Common Language Runtime). Strictly speaking, they are not a part of an operating system, we therefore will not use our favorite book for this lecture, but instead look at web references.

Heap Object Lifetime

Each object on the heap has a useful lifetime, from the moment it was allocated to the moment it was last used. By definition, at the end of its useful lifetime, the object only takes up heap space, which should be reclaimed. In traditional heap implementations, this is done by explicitly calling free (or delete or other explicit object deallocation operations in the target programming language). In managed heap implementations, the heap manager takes care of releasing the objects automatically.

The problem, of course, is how to efficiently and reliably recognize when an object reaches the end of its useful lifetime. In general, this would require forecasting future program behavior, which is not practically possible. The managed heap implementations therefore introduce a small but important tweak: rather than releasing objects that will no longer be used, objects are released when they can no longer be used.

To illustrate the difference, consider the following Java snippet:

public class AnExperiment {
    public static Object anObject;
    public static void aMethod () {
        Object anotherObject;
        anObject = new Object ();
        anotherObject = new Object ();
    }
}

When a caller invokes AnExperiment.aMethod, the program allocates two Objects, with references stored in variables anObject and anotherObject, respectively. Let us call these objects “first” and “second”.

When the method returns, both “first” and “second” are on the heap, but while “first” can still be accessed through the anObject variable, the program no longer has any variable that references “second” (the anotherObject variable was on the stack and ceases to exist when the method returns).

Without forecasting future program behavior, the managed heap implementation can therefore release “second”, but it cannot afford to release “first”, even though it may also well be at the end of its useful lifetime.

Garbage Collection Terminology

In technical parlance, heap objects are called live when usable by the program, and dead otherwise. Dead objects represent garbage and the responsibility of the heap implementation is to identify and garbage collect dead objects.

The garbage collector must determine what objects can still be referenced by the program. A program can obviously access any object whose reference is stored in a live variable, these variables are called roots. In addition to that, an object can be accessed by following references from another object, such objects are transitively reachable.

Two essential approaches to determining reachability are reference counting and reference tracing. With reference counting, the garbage collector expects the runtime environment to maintain a counter of references to each object. Obviously, when that count reaches zero for some object, then that object is not reachable. With reference tracing, the garbage collector traverses object references starting at the roots, and marks all objects encountered as reachable, the rest is not reachable.

Garbage Collection Algorithms

The useful knowledge we aim for is:

Obviously, there are many other sources, a lot of links can be found for example at https://wiki.c2.com/?GarbageCollection. Do not go too deep, there are actually whole books on garbage collection :-) …

In the following, System.gc denotes a method that explicitly triggers garbage collection. Also, please ignore the possibility that the compiler optimizes the entire example away (because the code has no externally visible actions), and assume it executes as written.

[Q] Look at the following code.

class Data {
    public Data neighbor;
}

public class Main {
    public static void main (String [] arguments) {
        Data a = new Data (); // Data object "first"
        Data b = new Data (); // Data object "second"
        a.neighbor = b;
        b = null;
        System.gc ();
    }
}

At the moment System.gc is called, which of the two allocated Data objects can be released ?

Hint ...

References, both direct and transitive from other live objects, keep an object live.

[Q] Look at the following code.

class Data {
    public Data neighbor;
}

public class Main {
    public static void main (String [] arguments) {
        Data a = new Data (); // Data object "first"
        Data b = new Data (); // Data object "second"
        a.neighbor = b;
        b.neighbor = a;
        a = null;
        b = null;
        System.gc ();
    }
}
Hint ...

References from dead objects do not make an object live.

[Q] For the purpose of this question, consider a hypothetical situation where you could convert between references and integers in Java as you can in C (the integer would contain the memory address that the reference points to). Look at the following code fragment:

public class Main {
    public static void main (String [] arguments) {
        Object o = new Object ();
        int a = reference_to_integer (o) + 1234;
        o = null;
        System.gc ();
        o = integer_to_reference (a - 1234);
    }
}

What would the ability to convert between references and integers (addresses) mean for garbage collection ?

[Q] What is a good time for the garbage collection to happen ?

[Q] Why are some garbage collectors called “stop the world” ?

Eager For More ?

Want to delve into the topic beyond the standard module content ? We’ve got you covered !