Garbage collection (computer science)

jaspersugarlandSoftware and s/w Development

Dec 14, 2013 (3 years and 10 months ago)

78 views


jaspersugarland_34ff7aba
-
ca7d
-
4099
-
bbe7
-
b8a62ca697aa.doc

1
/
6

Garbage collection (computer science)

From Wikipedia, the free encyclopedia

In computer science, garbage collection (also known as GC) is a form of automatic memory
management. The garbage collector or collector attempts to reclaim garbage, or memory used
by
objects that will never again be accessed or mutated by the application. Garbage collection was
invented by John McCarthy around 1959 to solve the problems of manual memory management in his
Lisp programming language.

Garbage collection is often portray
ed as the opposite of manual memory management, which requires
the programmer to specify which objects to deallocate and return to the memory system. However,
many systems use a combination of the two approaches, and there are other techniques being studie
d
(such as region inference) to solve the same fundamental problem. Note that there is an ambiguity of
terms, as theory often uses the terms manual garbage
-
collection and automatic garbage
-
collection
rather than manual memory management and garbage
-
collect
ion, and does not restrict garbage
-
collection to memory management, rather considering that any logical or physical resource may be
garbage
-
collected.

Description

The basic principle of how a garbage collector works is:


1. Determine what data objects in

a program will not be accessed in the future


2. Reclaim the storage used by those objects

By making manual memory deallocation unnecessary (and typically impossible), garbage collection
frees the programmer from having to worry about releasing objects
that are no longer needed, which
can otherwise consume a significant amount of design effort. It also aids programmers in their efforts
to make programs more stable, because it prevents several classes of runtime errors. For example, it
prevents dangling p
ointer errors, where a reference to a deallocated object is used. (The pointer still
points to the location in memory where the object or data was, even though the object or data has since
been deleted and the memory may now be used for other purposes, cre
ating a dangling pointer.)

Many computer languages require garbage collection, either as part of the language specification (e.g.
Java, C#, and most scripting languages) or effectively for practical implementation (e.g. formal
languages like lambda calculu
s); these are said to be garbage
-
collected languages. Other languages
were designed for use with manual memory management, but have garbage collected implementations
(e.g., C, C++). Some languages, like Modula
-
3, allow both garbage collection and manual me
mory
management to co
-
exist in the same application by using separate heaps for collected and manually
managed objects, or yet others like D, which is garbage
-
collected but allows the user to manually
delete objects and also entirely disable garbage collec
tion when speed is required. In any case, it is far
easier to implement garbage collection as part of the language's compiler and runtime system, but post
hoc GC systems exist, including ones that do not require recompilation. The garbage collector will
al
most always be closely integrated with the memory allocator.

Tracing garbage collectors

Tracing garbage collectors are the most common type of garbage collector. They focus on determining
which objects are reachable (or potentially reachable), and then dis
card all remaining objects.

Reachability of an object

Informally, a reachable object can be defined as an object for which there exists some name in the
program environment that leads to it, either directly or through references from other reachable
object
s. More precisely, objects can be reachable in only two ways:


jaspersugarland_34ff7aba
-
ca7d
-
4099
-
bbe7
-
b8a62ca697aa.doc

2
/
6


1. A distinguished set of objects are assumed to be reachable
--

these are known as the roots.
Typically, these include all the objects referenced from anywhere in the call stack (that is, al
l local
variables and parameters in the functions currently being invoked), and any global variables.


2. Anything referenced from a reachable object is itself reachable. This is referred to as transitivity.

The reachability definition of "garbage" is no
t optimal, insofar as the last time a program uses an
object could be long before that object falls out of the environment scope. A distinction is sometimes
drawn between syntactic garbage, those objects the program cannot possibly reach, and semantic
garb
age, those objects the program will in fact never again use. The problem of precisely identifying
semantic garbage can easily be shown to be partially decidable: a program that allocates an object X,
runs an arbitrary input program P, and uses X if and onl
y if P finishes would require a semantic
garbage collector to solve the halting problem. Although conservative heuristic methods for semantic
garbage detection remain an active research area, essentially all practical garbage collectors focus on
syntactic
garbage as described here.

Basic algorithm

Tracing garbage collectors use an algorithm in which they perform garbage collection cycles. A cycle
is started when the collector decides (or is notified) that it needs to reclaim storage, which in particular
hap
pens when the system is low on memory. All tracing garbage collectors implement some variant of
the tri
-
colour marking abstraction, but simple collectors (such as the mark
-
and
-
sweep collector) often
do not make this abstraction explicit. Tri
-
colour marking

works as follows:


1. Create initial white, grey, and black sets; these sets will be used to maintain progress during the
cycle. Initially the white set or condemned set is the set of objects that are candidates for having their
memory recycled. The bla
ck set is the set of objects that cheaply can be proven to have no references
to objects in the white set; in many implementations the black set starts off empty. The grey set is all
the remaining objects that may or may not have references to objects in t
he white set (and elsewhere).
These sets partition memory; every object in the system, including the root set, is in precisely one set.


2. (This step is repeated until the grey set is empty.) Pick an object from the grey set. Blacken this
object (move i
t to the black set), by greying all the white objects it references directly.


3. When there are no more objects in the grey set, then all the objects remaining in the white set are
probably not reachable and the storage occupied by them can be reclaimed
.

The tri
-
colour marking algorithm preserves an important invariant:


No black object points directly to a white object.

This ensures that the white objects can be safely destroyed once the grey set is empty.

Some variations on the algorithm do not pres
erve the tricolour invariant but they use a modified form
for which all the important properties hold.

Implementation strategies

In order to implement the basic tri
-
color algorithm, several important design decisions must be made,
which can significantly a
ffect the performance characteristics of the garbage collector.

Moving vs. non
-
moving

Once the unreachable set has been determined, the garbage collector may simply release the
unreachable objects and leave everything else as it is, or it may copy some or
all of the reachable
objects into a new area of memory, updating all references to those objects as needed. These are called
"non
-
moving" and "moving" garbage collectors, respectively.

At first, a moving GC strategy may seem inefficient and costly compared

to the non
-
moving approach,
since much more work would appear to be required on each cycle. In fact, however, the moving GC

jaspersugarland_34ff7aba
-
ca7d
-
4099
-
bbe7
-
b8a62ca697aa.doc

3
/
6

strategy leads to several performance advantages, both during the garbage collection cycle itself and
during actual program executi
on:



No additional work is required to reclaim the space freed by dead objects; the entire region of
memory from which reachable objects were moved can be considered free space.
In contrast, a
non
-
moving GC must visit each unreachable object and somehow rec
ord that the memory it alone
occupied is available.



Similarly, new objects can be allocated very quickly.
Since large contiguous regions of memory are
usually made available by the moving GC strategy, new objects can be allocated by simply
incrementing a '
free memory' pointer. A non
-
moving strategy may, after some time, lead to a
heavily fragmented heap, requiring expensive consultation of "free lists" of small available blocks
of memory in order to allocate new objects.



If an appropriate traversal order is

used, objects that refer to each other frequently can be moved
very close to each other in memory, increasing the likelihood that they will be located in the same
cache line or virtual memory page.
This can significantly speed up access to these objects t
hrough
these references.

Copying vs. mark
-
and
-
sweep

To further refine the distinction, tracing collectors can also be divided by considering how the three
sets of objects (white, grey, and black) are maintained during a collection cycle.

The most straightf
orward approach is the semi
-
space collector, which dates to 1969. In this moving
GC scheme, memory is partitioned into a "from space" and "to space". Initially, objects are allocated
into "to space", until it becomes full and a collection is triggered. At
the start of a collection, the "to
space" becomes the "from space", and vice versa. The objects reachable from the root set are copied
from the "from space" to the "to space". These objects are scanned in turn, and all objects that they
point to are copied

to "to space", until all reachable objects have been copied to "to space". Once the
program continues execution, new objects are once again allocated from the "to space" until it is once
again full and the process is repeated. This approach has the advant
age of conceptual simplicity (the
three object color sets are implicitly constructed during the copying process), but the disadvantage that
a (possibly) very large contiguous region of free memory is necessarily required on every collection
cycle.

A "mark
and sweep" garbage collector maintains a bit (or two) with each object to record whether it is
white or black; the grey set is either maintained as a separate list or using another bit. As the reference
tree is traversed during a collection cycle, these bi
ts are manipulated by the collector to reflect the
current state. The mark and sweep strategy has the advantage that, once the unreachable set is
determined, either a moving or non
-
moving collection strategy can be pursued; this choice of strategy
can even

be made at runtime, as available memory permits. It has the disadvantage of "bloating"
objects by a small amount.

Generational GC (aka Ephemeral GC)

It has been empirically observed that in many programs, the most recently created objects are also
those m
ost likely to quickly become unreachable (known as infant mortality or the generational
hypothesis). A generational GC divides objects into generations and, on most cycles, will place only
the objects of a subset of generations into the initial white (cond
emned) set. Furthermore, the runtime
system maintains knowledge of when references cross generations by observing the creation and
overwriting of references. When the garbage collector runs, it may be able to use this knowledge to
prove that some objects i
n the initial white set are unreachable without having to traverse the entire
reference tree. If the generational hypothesis holds, this results in much faster collection cycles while
still reclaiming most unreachable objects.

In order to implement this co
ncept, many generational garbage collectors use separate memory regions
for different ages of objects. When a region becomes full, those few objects that are referenced from
older memory regions are promoted (copied) up to the next highest region, and the
entire region can

jaspersugarland_34ff7aba
-
ca7d
-
4099
-
bbe7
-
b8a62ca697aa.doc

4
/
6

then be overwritten with fresh objects. This technique permits very fast incremental garbage
collection, since the garbage collection of only one region at a time is all that is typically required.

Generational garbage collection is a heu
ristic approach, and some unreachable objects may not be
reclaimed on each cycle. It may therefore occasionally be necessary to perform a full mark and sweep
or copying garbage collection to reclaim all available space. In fact, runtime systems for modern
programming languages (such as Java and the .NET Framework) usually use some hybrid of the
various strategies that have been described thus far; for example, most collection cycles might look
only at a few generations, while occasionally a mark
-
and
-
sweep i
s performed, and even more rarely a
full copying is performed to combat fragmentation. The terms "minor cycle" and "major cycle" are
sometimes used to describe these different levels of collector aggressiveness.

Stop
-
the
-
world vs. incremental vs. concurren
t

Simple stop
-
the
-
world garbage collectors completely halt execution of the program to run a collection
cycle, thus guaranteeing that new objects are not allocated and objects do not suddenly become
unreachable while the collector is running. This has the
obvious disadvantage that the program can
perform no useful work while a collection cycle is running (sometimes called the "embarrassing
pause").

Incremental garbage collectors are designed to reduce this disruption by interleaving their work with
activity

from the main program. Careful design is necessary to ensure that the main program does not
interfere with the garbage collector and vice versa; for example, when the program needs to allocate a
new object, the runtime system may either need to suspend it

until the collection cycle is complete, or
somehow notify the garbage collector that there exists a new, reachable object.

Finally, a concurrent garbage collector can run concurrently in real time with the main program on a
symmetric multiprocessing machi
ne. Complex locking regimes may be necessary in order to guarantee
correctness, and cache issues also make this less helpful than one might imagine. Nonetheless,
concurrent GC may be desirable for SMP applications with high performance requirements.

Precis
e vs. conservative and internal pointers

Some collectors can correctly identify all pointers (references) in an object; these are called "precise"
(or "exact" or "accurate") collectors, the opposite being a "conservative" or "partly conservative"
collector
. "Conservative" collectors have to assume that any bit pattern in memory is a pointer if
(when interpreted as a pointer) it would point into any allocated object. Thus, conservative collectors
may have some false negatives, where storage is not released b
ecause of accidental fake pointers, but
this is rarely a significant drawback in practice. Whether a precise collector is practical usually
depends on type safety properties of the programming language.

A related issue concerns internal pointers, or pointe
rs to fields within an object. If the semantics of a
language allow internal pointers, then there may be many different addresses that can refer to the same
object, which complicates determining whether an object is garbage or not.

Performance implications

Tracing garbage collectors require some implicit runtime overhead that may be beyond the control of
the programmer, and can sometimes lead to performance problems. For example, the tendency of the
runtime system to pause program execution at arbitrary tim
es to run the garbage collector may make
GC languages inappropriate for some embedded systems, high
-
performance server software, and
applications with real
-
time needs.

A more fundamental issue is that garbage collectors violate locality of reference, since

they
deliberately go out of their way to find bits of memory that haven't been accessed recently. The
performance of modern computer architectures is increasingly tied to caching, which depends on the
assumption of locality of reference for its effectiven
ess. Some garbage collection methods result in
better locality of reference than others. Generational garbage collection is relatively cache
-
friendly,
and copying collectors automatically defragment memory helping to keep related data together.

jaspersugarland_34ff7aba
-
ca7d
-
4099
-
bbe7
-
b8a62ca697aa.doc

5
/
6

Nonetheless
, poorly timed garbage collection cycles could have a severe performance impact on some
computations, and for this reason many runtime systems provide mechanisms that allow the program
to temporarily suspend, delay or activate garbage collection cycles.

De
spite these issues, for many practical purposes, allocation/deallocation
-
intensive algorithms
implemented in modern garbage collected languages can actually be faster than their equivalents using
explicit memory management (at least without heroic optimiza
tions by an expert programmer). A
major reason for this is that the garbage collector allows the runtime system to amortize allocation and
deallocation operations in a potentially advantageous fashion. For example, consider the following
program in the (ga
rbage
-
collected) C# language:

class A {


private int x;


public A() { x = 0; ++x; }

}

class Example {


public static void Main() {


for (int i = 0; i < 1000000000; ++i) {


A a = new A();


}


System.Console.WriteLine("DING!");


}

}

And a n
early equivalent C++ program:

#include <iostream>


class A {


int x;

public:


A() { x = 0; ++x; }

};

int main() {


for (int i = 0; i < 1000000000; ++i) {


A *a = new A();


delete a;


}


std::cout << "DING!" << std::endl;

}

Using standard librari
es and typical compiler configurations, the C# program executes many times
faster than the C++ program. This is because the C++ allocator must hunt for free blocks of memory in
a potentially fragmented heap in order to allocate new instances of the A class
. In contrast, the C#
runtime system can allocate memory by incrementing a pointer from some region of memory
previously set aside for new allocations, relying on the garbage collector to eventually release the
unused objects and compact the heap. On the o
ther hand, the garbage
-
collected program may have
somewhat greater memory usage averaged over time, since instances of the A class are not deallocated
as quickly as they could be.

Each has different sorts of overheads:


Manual allocation:



search for bes
t/first
-
fit block of sufficient size



free list maintenance


Garbage collection:



locate reachable objects



copy reachable objects for moving collectors



read/write barriers for incremental collectors


jaspersugarland_34ff7aba
-
ca7d
-
4099
-
bbe7
-
b8a62ca697aa.doc

6
/
6



search for best/first
-
fit block and free list maintenanc
e for non
-
moving collectors

It is difficult to compare the two cases directly, as their behavior depends on the situation. For
example, in the best case for a garbage
-
collecting system, allocation just increments a pointer; but in
the best speed performanc
e case for manual allocation, the allocator maintains freelists of specific
sizes and allocation only requires following a pointer. However this size segregation usually cause a
large degree of external fragmentation and this can impact cache behaviour.

Th
e overhead of write barriers is more likely to be noticeable in an imperative
-
style program which
frequently writes pointers into existing data structures than in a functional
-
style program which
constructs data only once and never changes them.

Some advan
ces in garbage collection can be understood as reactions to performance issues. Early
collectors were stop
-
the
-
world collectors, but the performance of this approach was distracting in
interactive applications. Incremental collection avoided this disruptio
n, but at the cost of decreased
efficiency due to the need for barriers. Generational collection techniques are used with both stop
-
the
-
world and incremental collectors to increase performance
--
the trade
-
off is that some garbage is not
detected as garbage
for longer than normal.

Reference counting

In contrast to tracing garbage collection, reference counting is a form of automatic memory
management where each object has a count of the number of references to it. An object's reference
count is incremented wh
en a reference to it is created, and decremented when a reference is destroyed.
The object's memory is reclaimed when the count reaches zero.

There are two major disadvantage to reference counting:



If two objects refer to each other, they can create a cycl
e whereby neither will be collected as their
mutual references never let their reference counts become zero.
Some GC systems using reference
counting use specific cycle
-
detecting algorithms to deal with this issue.



Each assignment of a reference and each r
eference falling out of scope require modifications of one
or more reference counters.
When used in a multithreaded environment, these modifications
(increment and decrement) need to be interlocked. This is usually a very expensive operation.

Implementatio
ns

GC comes either as a part of a programming language or an external library that can be added to a
language that does not have built
-
in support of GC. Boehm garbage collector is the chief example for
the latter.

Functional programming languages, like ML,

Haskell and Lisp, traditionally use garbage collection.
Lisp, which introduced functional programming, is especially notable for using garbage collection
before this technique was commonly used. Script languages like Perl, Ruby, Python and PHP tend to
hav
e built
-
in support of GC. Also, many object
-
oriented programming languages like Smalltalk, Java
and ECMAScript usually provide integrated garbage collection, a notable exception being C++.