Garbage Collection

10. Garbage Collection

This section describes the garbage collection issues and implementation involved with both the persistent object store and how it interacts with the transient heap.

There are some limitations on the use of the persistent garbage collector. Currently, it does not understand lazy pointers (although lazy pointers are disabled in the current pstore implementation anyway). Also, it does not correctly handle the entry slot of an allocation area. If using explicit allocation areas to manage storage, the application must be sure to maintain a pointer to each persistent allocation area in use or else it may be erroneously reclaimed.

Because the separation of the two heaps is largely transparent to the application, the system must ensure GC correctness in the presence of many cross-heap pointers and in the presence of adversarial mutator (application) activity.

Correctness is defined as never reclaiming an object that could possibly still be used by the application. An object could possibly be used if there is some path to it from a root. In RScheme, the roots consist of the virtual machine registers, the stack cache, and the root vectors for each loadable module.

In RScheme, the transient and persistent garbage collections are both incremental. This means that the application continues to run while GC activity happens behind the scenes. This greatly complicates the implementation (especially in the presence of two heaps!) but provides the considerable benefit of allowing the application to avoid any long pauses. In order to achieve incrementality, RScheme uses a write barrier to notice when application activity might interfere with the correctness of an ongoing garbage collection cycle.

A persistent store does not, by default, have garbage collection enabled for it. To turn on GC for a persistent store, call (start-online-compacter pstore). Online compaction and GC is only applicable to pstores that have two LSS volumes, because part of the process involves compacting the underlying LSS.

10.1. Transient Garbage Collection

It is possible for an uncommited persistent object store to hold the only pointer to a transient object. Hence, correctness requires that the system keep track of pointers from persistent objects into the transient heap. This type of tracking is typical for any generational garbage collector, and these kinds of pointers represent intergenerational pointers. Here, the uncommitted state of the persistent store acts as an older generation which may contain pointers into a younger generation.

As an example, consider Figure 8. Persistent object f contains the only reference to transient object h, and persistent object k the only reference to transient object l. If the system did not notice that f has a pointer to h, then the storage for h might be reclaimed as garbage. A subsequent traversal through e and f would likely crash the system.

The system handles pointers from the persistent heap by using a write barrier on persistent objects. (This is the GC-level write barrier that is usually used to detect attempts to violate the tricolor marking invariant, and bears no relation to the page-level write protection used to detect dirty pages of the persistent store.) In RScheme's native garabage collector (called "IRC", for "Implicit Reclamation Collector"), the write barrier is implemented as a matrix lookup-table keyed by the lvalue generation number and color on one axis and the rvalue generation number and color on the other axis. Since the transient GC is not currently generational, only generation number 0 corresponds to the transient heap. Generation number 7 is used to denote a persistent heap. Figure 5 illustrates the different kinds of pointer relationships being created by a WRITE operation, and where they fit in the write barrier matrix.

Figure 2. The Six Phases of Persistent GC
[The six phases of persistent GC: prep, pending, tscan, pscan, reclaim, and idle]
Figure 3. Experimental Data: Disk Usage During Persistent GC Phases
[A graph showing the disk space occupied by two volumes as the persistent GC proceeds]

We wait for a commit on the pscan to reclaim transition so that the extraHeapPointers list is flushed. Otherwise, there could be a down-pointer slot in an object that is reclaimed, which would cause the transient GC to crash when scanning the extraHeapPointers list.

We have to be careful when deallocating objects. Consider, for example, Figure 4, where a is the root object and b and c are both dead objects, and c is the only object on page 202. When c is deallocated, page 202 is unmapped from memory. Then, even though b is dead, if a is touched and page 201 is written out, the system will try to scan b as if it is live, causing an illegal reference to the missing page 202.

Furthermore, we want to do reclamation incrementally, but yet need to provide a consistent persistent view of objects -- specifically, that no committed unfreed object refer to a freed object. Although there are solutions that are more efficient in some cases (e.g., by looking for connected subgraphs), the most straightforward approach to handle this problem is to free objects in two passes. In the first pass, all pointers are deleted. In the second pass, the objects themselves are deallocated.

Figure 4. Illustrating the Ordering Problem for Object Reclamation
[Two pages in memory with three objects, a and b on one, and c on another]
Figure 5. How Pointer Relationships Fit Into the Write Barrier Matrix
[A 4x4 matrix, rvalue horizontally, lvalue vertically, showing the write barrier matrix in terms of generation and color of each pointer]
Figure 6. How the Write Barrier Matrix is Configured During the TSCAN Phase
[A write barrier matrix showing that storing a pointer to a WHITE persistent object triggers the write barrier]

When the program stores any pointer to a transient object into a persistent object, the WB_PERSISTENT write barrier is tripped, causing the location of the slot in persistent storage (the address of that slot is known as a pos_ptr_addr) to be stored in the extraHeapPointers list.

Figure 7. The Use of extraHeapPointers to Track Slots Containing Pointers to Transient Objects
[A persistent object, A, having a slot containing a pointer to transient object B]

Consider Figure 7 for an example, in which persistent object A refers to transient object B. By keeping track of the persistent slot that holds a pointer to B, we allow subsequent mutation to drop the pointer to B and allow it to be reclaimed in the current transient GC cycle. We can't just mark B as gray because then there would be no way to find the pointer to B again in the next transient GC cycle.

Furthermore, we don't want to keep track of the whole persistent object A, because it may be a large object containing many pointers to scan.

An alternative approach may be to do this at the page level, since only dirty pages can contain pointers into the transient heap -- we could therefore elide the GC write barrier and use the dirty page list. That would bound the scanning work to the page size. But then, for applications that mutate heavily without pointing to very many tranient objects, this would create a heavy load on the transient GC. Remember, considering something as part of the root set means it must be atomically scanned when the GC cycle is attempting to terminate.

When the transient GC is traversing its roots, it regards each pstore's extraHeapPointers list as part of its root set.

When the pstore is committed, we know that there are no pointers from the pstore into the transient heap other than pivot objects. Since pivot objects live in the pstore's indirects table, we can be certain that the only pointer to the transient object is not being held by a persistent page. Thus the extraHeapPointers can be flushed when the pstore is successfully committed.

10.1.1. Write Barrier Processing

Because the transient GC allocates white and it is known that the (transient) write barrier is unnecessary for writes into white objects, the IRC exposes the notion of "fresh" objects, which are objects that have been allocated since the last safe point. There are special write operators for initializing writes ( gvec_write_init ) and for writes to fresh objects ( gvec_write_fresh ). Under IRC, these operators are no-ops, avoiding the cost of the write barrier check for the very common operation of initializing a new object. Unfortunately, in the presence of a persistent store, the assumption of a noop write barrier for fresh objects is not true.

Fortunately, whenever an object is allocated from the persistent store, it is done through the specialized alloc_in_area() function. So, to support the persistent store, any procedure using alloc_in_area() must do initializing writes with non-pointer (i.e., immob) objects and use the full gvec_write() API to supply any pointer values.

It would be redundant to store a given persistent slot in the extraHeapPointers list more than once. However, the write barrier is configured to trigger on every store into the persistent heap. In order to eliminate redundant slot references in the extraHeapPointers list, the pstore keeps track of which slots have already been added to the list. This is done in two different ways, depending on the size of the object. For small objects (no more than 24 slots), a 24-bit field in the object header is used. For larger objects, a "card" indexed bitmap is employed.

10.2. Online Persistent Garbage Collection

RScheme includes a facility for performing a garbage collection cycle of the persistent object store while the store is currently open. This enables the implementation of long-running servers while preserving the ability to reclaim garbage within the store.

The implementation must make sure not to reclaim objects that are referenced only by the transient heap (i.e., are not reachable from the persistent root).

Consider Figure 8.

Figure 8. Data Structures Spanning Transient and Persistent Heaps
[Two data structures, (a b c d) rooted in the transient heap, (e f g h i j k l) rooted in the persistent heap, each spanning both the transient and persistent heaps]
Figure 9. A state of the persistent and transient heaps before GC starts
[A transient heap with references to persistent objects]
Post feedback about this page... Last modified 2012-12-23 13:23:51 UTC (2.04e+03 d ago)