02 GarbageCollection
02 GarbageCollection
Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Motivation
Manual memory deallocation is error-prone (e.g. in C/C++)
1. deallocation too early
p
q
dealloc(p);
"dangling pointer"
q.f = ...;
"memory leak"
object becomes unreachable
but wastes space
p = ...;
Garbage collection
A block is automatically reclaimed as soon as it is not referenced any more.
p
p
p = q;
References (pointers)
When are new references created?
object creation:
p = new Person();
p
assignment:
q = p;
parameter passing:
foo(p);
void foo(Object r) {...}
q
r
p = s;
void foo(Object p) {
...
}
reclamation of an object
that contains pointers:
p = null;
Example
A a = new A();
B b = new B(a);
class B {
private A p;
public B(A a) { p = a; }
...
Languages with GC
Java, C#, Eiffel, Smalltalk,
Lisp, Scheme, Scala, Oberon, ...
Languages without GC
C, C++, Pascal, Fortran,
Cobol, ...
4
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Reference Counting
Oldest garbage collection technique (1960: Lisp)
Idea
Every object has a counter holding the number of pointers that reference this object
count
data
3
count ... hidden counter field
Counter management
Compiler generates code for updating the counters
if (q != null) q.count++;
assignments
p = q;
IncRef(q);
DecRef(p);
if (p != null) {
p.count--;
if (p.count == 0) dealloc(p);
}
IncRef(p);
...
DecRef(p);
deallocation of objects
void dealloc(Object p) {
for (all pointers q in *p) DecRef(q);
...
}
p = null;
a.count--;
b.count--;
c.count--;
Strengths
+ Unreferenced blocks are immediately reclaimed
(no delay like in other GC algorithms)
+ GC does not cause major pauses
(GC overhead is evenly distributed over the whole program)
+ GC can be done incrementally
DecRef(p)
background thread
if (p != null) {
p.count--;
if (p.count == 0) worklist.add(p);
}
while (!worklist.empty()) {
p = worklist.remove();
dealloc(p);
}
Weaknesses
- Pointer assignments and parameter passing impose some overhead
(GC costs are proportional to the number of assigments, even if there is no garbage)
- Counters need space in every object (4 bytes)
- Does not work for cyclic data structures!
p
p = null;
a.count--;
b.count--;
void decRef(Obj p) {
p.count--;
if (p.count == 0) {
p.color = white;
for (all sons q) decRef(q);
dealloc(p);
} else if (p.color != red) {
p.color = red;
gcList.add(p);
}
}
void incRef(Obj p) {
p.count++;
p.color = black;
}
p
after mark(p)
0
p
after sweep(p)
0
12
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Idea
roots
"live" pointer variables, which are not on the heap
(e.g. local variables)
heap
14
Disadvantages
-
Related objects are often spread over the whole heap (poor cache behavior).
15
Naive implementation
Simplistic assumption
all objects have the same type
class Block {
boolean marked;
Block left, right;
... // data
}
Mark algorithm
1
5
4
Deutsch-Schorr-Waite algorithm
Schorr, Waite: An efficient machine-independent procedure for garbage collection
in various list structures, Communications of the ACM, Aug. 1967
Idea
Pointers are followed iteratively not recursively
The backward path is stored in the pointers themselves!
Example
prev
cur
17
n
i
son
// number of pointers
// index of currently visited pointer
// pointer array
// data
i == -1
block is still unvisited;
used for marking
4
2
0
1
2
3
already visited
currently under visit
unvisited
data
18
prev
advance
cur
retreat
prev
cur
when
cur.i == cur.n
cur
p = cur.son[cur.i];
cur.son[cur.i] = prev;
prev = cur;
cur = p;
pointer rotation
p
cur.son[cur.i]
cur
prev
p = cur;
cur = prev;
prev = cur.son[cur.i];
cur.son[cur.i] = p;
pointer rotation
p
cur.son[cur.i]
cur
prev
19
DSW algorithm
void mark (Block cur) {
// assert cur != null && cur.i < 0
Block prev = null;
for (;;) {
cur.i++; // mark
if (cur.i < cur.n) { // advance
Block p = cur.son[cur.i];
if (p != null && p.i < 0) {
cur.son[cur.i] = prev;
prev = cur;
cur = p;
}
} else { // retreat
if (prev == null) return;
Block p = cur;
cur = prev;
prev = cur.son[cur.i];
cur.son[cur.i] = p;
}
}
}
20
Example
prev
cur
advance
prev
advance
retreat
prev
cur
prev
cur
cur
advance
retreat
prev
prev
retreat
prev
cur
advance
prev
cur
cur
cur
retreat
prev
cur
retreat
ready!
21
Type descriptors
Allow pointers to be at arbitrary locations in an object
class Block {
Block x;
Block y;
int data;
Block z;
}
Block a = new Block();
Block b = new Block();
0
4
8
12
tag
x
y
data
z
tag
x
y
data
z
type descriptor
of Block
20
objsize (=20)
0
4
12
sentinel
pointer
offsets
other
information
type descriptors are generated by the compiler for all classes; they are written to the object file
the loader allocates the type descriptors on the heap
new Block() allocates an object and installs in it a pointer to the corresponding type descriptor
22
Type tags
Format of a type tag
31
0
pointer to type descriptor
tag
objsize
next
objsize
23
type descriptor
size
off
...
...
-16
cur
padr
off
16
24
Sweep phase
Heap after the mark phase
free list
25
Sweep algorithm
void sweep() {
Pointer p = heapStart + 4;
Pointer free = null;
while (p < heapEnd) {
if (p.marked) p.marked = false;
else { // free: collect p
int size = p.tag.size;
Pointer q = p + size;
while (q < heapEnd && !q.marked) {
size += q.tag.size; // merge
q = q + q.tag.size;
}
p.tag = p; p.tag.size = size;
p.tag.next = free; free = p;
}
p += p.tag.size;
}
}
allocated block
p
tag
size
size
free block
p
tag
size
next
26
Lazy sweep
Problems
Sweep must visit every block (takes some time if the heap is hundreds of megabytes large)
In virtual memory systems any swapped pages must be swapped in and later swapped out again
scan
p = alloc(size);
free
marked
unmarked
free
free
scan
free
scan
27
scan
free
scan
free
scan
Requirements
Mark bits remain set while the program (the mutator) runs
(they must be masked out when the type tag is accessed)
mark() must only be restarted after the whole sweep has ended
28
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
top
toSpace
fromSpace
Simple sequential alloc(size)
top = top + size;
g
top
fromSpace
toSpace
30
Scavenging (phase 1)
a
b
scan
fromSpace
d
top
toSpace
31
Scavenging (phase 2)
2. Move the scan pointer through the objects in toSpace
a
scan
top
d
top
scan
f
top
scan
a
ready if
scan == top
f
top
scan
32
Scavenging (phase 3)
3. Swap fromSpace and toSpace
b
f
top
toSpace
fromSpace
Advantages
Disadvantages
single-pass algorithm
no mark()
purely sequential; no graph traversal
heap is compacted
(no fragmentation, better locality)
simple alloc()
run time independent of heap size;
depends only on the number of live objects
33
Overheads
RC
***
M&S
S&C
***
*
***
34
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Sweep 1: For every marked block compute its address after compaction
0
16
32
Sweep 2: Adjust roots and pointer fields to point to the new addresses
Sweep 3: Move blocks to the computed addresses
16
32
Advantages
Disadvantages
+
+
+
+
removes fragmentation
simple sequential alloc()
order of objects on the heap is retained
good locality (virtual memory, caches)
36
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Solution
newFrom
top1
Young generation
(short-living objects)
newTo
Old generation
(long-living objects)
top2
oldFrom
oldTo
38
Cross-generation pointers
Pointers from newFrom to oldFrom
newFrom
newTo
oldFrom
oldTo
newTo
oldFrom
oldTo
remembered set
39
Write barriers
Problem: pointer assignments
new
new
newFrom
old
newFrom
old.f = new;
oldFrom
old
oldFrom
Tenured garbage
Dead objects in the old generation
newFrom
oldFrom
Tenured garbage may keep dead objects in the young generation alive
=> should be avoided if possible
Tenuring threshold
n: Number of copy runs until an object is copied to oldFrom
Dilemma
n small => much tenured garbage
n large => long-living objects remain in young generation longer than necessary
=> long GC times
41
Age table
How many bytes of every age are in newFrom?
age
bytes
1
2
3
50000
9000
8000
Watermark
Assume that the tolerated maximum GC pause is 1 ms
=> watermark = max. number of bytes, which can be copied in this time
42
=> tenureAge =
watermark
=> tenureAge must be chosen such that at least d bytes are tenured
in the next GC run
1
2
3
50000
9000
8000
43
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Motivation
Goal
Avoid long GC pauses (pauses should be less than 1 ms)
Application areas
For soft real-time systems (hard real-time systems should not use a GC at all)
For the old generation in a generational GC
GC pause is longer for the old generation because:
- old generation is larger than young generation
- there are more live objects in the old generation than in the young generation
Idea
GC (collector) and application program (mutator) run in parallel (interleaved)
a) collector runs continuously as a background thread
b) collector stops the mutator, but does only a partial collection
Problem
Mutator interferes with the collector!
Can change data structures, while the collector is visiting them
45
yes
no
a
b
a.f = d;
c
no
b is kept alive
d is collected!
46
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Tricolor marking
Abstraction of an incremental GC, from which various concrete algorithms can be derived
toSpace
scan
top
Invariant
There must never be a pointer from a black object to a white object
48
Problem source
Mutator interferes
a.next = c;
b.next = null;
Collector continues
a
Problem solution
We have to avoid that a black object points to a white object
a
a.next = c;
b.next = null;
Write barrier
If a white object is installed into a black object
the black object is "greyed"
(i.e. it must be revisited)
a.next = c;
d'
scan top
d'
scan top
new
b'
d'
scan top
new
51
b'
d'
scan top
new
4. If the mutator accesses a white object, this object is copied and becomes grey
(read barrier)
e.g. after accessing a
a'
b'
d'
scan
top new
Problems
- Requires a read barrier for every read access to a white object (20% of the run time)
- Can only be implemented efficiently with special hardware or OS support (virtual memory)
52
Problematic case
p
p.f = q;
q = ...;
Snapshot at beginning
Objects stay alive, if they were reachable at the beginning of the GC run
p
p.f = q;
obj
q
obj was reachable and thus
must stay alive
obj
Implementation:
ptr = ...;
worklist.add(ptr);
ptr = ...;
Catches all pointer assignments (not only assignments to pointers in black objects)
Very conservative!
54
Incremental update
Objects stay alive, if they are reachable at the end of the GC run
p
a) p.f = q;
q
obj
obj
if q disappears,
obj is visited nevertheless
if a pointer to a white object is
installed in a black object
the white object is greyed
55
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Idea
Incremental Stop&Copy algorithm with write barriers (primarily for old generation)
The heap is divided into segments ("cars") of fixed size (e.g. 64 KBytes)
"car"
"car"
"car"
"train"
Every car has a remembered set:
addresses of pointers from later cars
to this car (= additional roots)
Objects that are larger than 1 car are managed in a separate heap
(large object area)
57
Simple solution
All objects of a dead cycle must be copied into the same car
...
Train algorithm
Cars are grouped into several trains
1.1
1.2
1.3
train 1
2.1
2.2
train 2
3.1
3.2
3.3
3.4
train 3
Order of processing: 1.1 < 1.2 < 1.3 < 2.1 < ... < 3.3 < 3.4
train 2
If no object in the first train is referenced from outside the first train
=> release the whole first train!
59
Remembered sets
Remembered set of a car x
Contains addresses of pointers from later cars to x
a
c
b
c d
60
p
f
p.f = q;
p.f
Remembered Set
61
Car ordering
Cars are logically ordered!
Their physical addresses need not be in ascending order (cars may be anywhere in the heap)
train 1 1.1
1.2
40000 10000
train 2 2.1
1.3
20000
physical addresses
(hexadecimal)
2.2
50000 00000
train 3 3.1
3.2
70000 90000
3.3
3.4
30000
80000
Car table
maps physical address to car number
e.g. car size 2k (e.g. 216 bytes = 64 KBytes)
car index n = (adr - heapStart) >> k;
tab[n] tells us which car is stored at adr
n
0 0000
1 0000
2 0000
3 0000
4 0000
5 0000
6 0000
7 0000
8 0000
9 0000
10 0000
tab
2.2
1.2
1.3
3.3
1.1
2.1
3.1
3.4
3.2
...
Example
pointer from 30AF4 to 50082
from tab[3] to tab[5]
from 3.3 to 2.1
from back to front
62
Incremental GCstep
r
if (there are no pointers to the first train from outside this train)
release the whole first train;
else {
car = first car of first train;
copy
for (all p in rememberedSet(car)) {
copy pobj to the last car of train(p);
if full, start a new car in train(p);
}
for (all roots p that point to car) {
copy pobj to the last car of the last train (not to the first train!);
if full, start a new train;
}
scan
for (all p in copied objects)
if (p points to car) {
copy pobj to last car of train(p);
if full, start a new car in train(p);
} else if (p points to a car m in front of car(p))
add p to rememberedSet(m);
release car;
}
Additional considerations
How to find pointers from outside
this train: inspect roots and
remembered sets of all cars
of this train.
If there are multiple pointers to
the same object => don't copy
this object twice, but install a
forwarding pointer.
Cars and trains must be linked
in order to find the first and the
last car of a train.
63
Example
B, F
root
R
copy R to the last car of the last train (because it is referenced from a root)
copy A to the last car of train(B)
copy C to the last car of train(F)
B
root
A
64
Example (continued)
R, C
B
root
copy S to the last car of train(R); no space => start a new car in train(R)
copy D to the last car of train(C); no space => start a new car in train(C)
copy E to the last car of train(D)
T
F
B
root
A
S
65
Example (continued)
S, E
T
F
B
root
B
root
A
S
66
Example (continued)
C
F
D
B
root
A
S
no pointers to the first train from ouside this train => release the whole first train
67
Example (continued)
B
root
A
S
root
68
Example (continued)
B
A
S
root
69
Example (continued)
R
root
T
root
ready!
Only live objects survived
In every step at least 1 car was released => progress is guaranteed
70
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Root pointers
Roots are all live pointers outside the heap
local variables on the stack
reference parameters on the stack (can point to the middle of an object!)
global variables in C, C++, Pascal, ...
(static variables in Java are on the heap (in class objects))
registers
stack
registers
global variables
heap
All objects that are (directly or indirectly) referenced from roots are live
Mark & Sweep:
for (all roots p) mark(p);
sweep();
72
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Global pointers
Global pointer variables in Oberon
For every module the compiler writes a list of global pointer addresses to the object file
The loader creates a pointer offset table for every loaded module
module A
module B
module C
74
Local pointers
For every method the compiler generates a table with pointer offsets
void foo(Obj a, int b) {
int c;
Obj d;
...
}
// a ... Offset 0
// d ... Offset 3
foo()
bar()
baz()
1000
1250
75
if (...) {
int a;
Obj b;
...
} else {
Obj c;
int d;
}
0
1
a
b
0
1
c
d
pc2
pc3
pc4
to
pointer offsets
pc1
pc2
pc3
pc4
76
Pointers in objects
class Person {
int id;
String name;
String address;
int zip;
}
0
1
2
3
tag
id
name
address
zip
type descriptor
1
2
pointer offsets
77
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
candidates
i
heap
80
Heap
0
32
64
96
128
allocation bitmap
bit
b
= b - (heapStart >> 5)
Bitmap requires 1 bit per 32 bytes (256 bits) => 1/256 = 0.4% of the heap
alloc() must set the bits
sweep() must reset the bits
for (all words w in stack, global data and registers) {
if (heapStart <= w < heapEnd && bit[w >> 5] && !w.marked) mark(w);
}
+
+
-
81
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
GC in multi-threaded systems
Problems
Several mutator threads -- all must be stopped before GC runs
GC is not allowed at all locations, e.g.
MOV EAX, objAdr
ADD EAX, fieldOffset
MOV [EAX], value
Safepoint polling
Mutator checks at safepoints if GC is pending
Safepoints
or:
MOV dummyAdr, 0
Thread1
...
PC ...
...
...
...
...
Thread2
...
...
...
...
Safe...
point
...
84
Safepoint patching
Safepoints are patched with instructions that cause a thread to be suspended
Safepoints
method call
method exit (return)
backward jump
call trap
call trap
85
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Goal
Cleanup work to be done before an object is reclaimed
closing open files
terminating threads
e.g. in Java
class T {
...
protected void finalize() throws Throwable {
... // cleanup work
super.finalize();
}
}
87
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
Heap layout
Two generations
young generation
fromSpace
nursery
toSpace
Stop&Copy
New objects are allocated in the nursery
if full => copy fromSpace + nursery to toSpace
advantage: less waste for toSpace
if overflow => copy remaining objects to old
after n copy passes an object is copied to old
n is variable (adaptive tenuring)
many new objects => faster "tenuring"
old generation
Mark&Compact
is executed less frequently
Remembered set
List of all pointers from old to young
An entry is made
- if an object with pointers to other young objects is tenured
- if a pointer to young is installed in an old object (detected by a write barrier)
91
Write barriers
Card-marking scheme
heapStart
heap
divided into
"cards" of 512 bytes
dirty
= a - (heapStart >> 9)
dirty table: 1 byte(!) per card (byte ops are faster than bit ops)
1 ... card unmodified
0 ... card modified
If a pointer is installed into an object: obj.f = ...; (no matter where it points to):
LEA
MOV
SHR
MOV
ADD
MOV
EAX, obj
EBX, EAX
EBX, 9
byte ptr dirty[EBX], 0
EAX, offset(f)
[EAX], ...
d2
d1 d2
offset table
1 byte per card
how far does the first object extend into the predecessor card?
if it overlaps the whole predecessor card:
offset = 255 => search one card before
Objects are aligned to 4 byte boundaries
=> table holds offset / 4
27
28
29
32
obj
27
27
1
cardEnd
28
8
29
28
0
29
1
offset
dirty
93
Object layout
class A {
Obj x;
int y;
Obj z;
}
class B extends A {
int a;
Obj b;
Obj c;
}
aging
4
mark, lock
26
tag
x
z
y
b
c
a
2
2
2
94
If GC is pending, the memory page dummyAdr is made readonly => trap => suspend()
95
G1 -- Garbage-first collector
Alternative GC for server applications (large heaps, 4+ processors)
Since Java 6
Main ideas
1. Incremental GC (similar to train algo)
A
...
RSA
RSB
RSC
250
270
3. Allocate new objects in "current region" (if full, start new current region)
96
mark (p) {
if (*p not marked) {
mark *p;
todo.add(p);
}
}
97
q
f
p.f = q;
p.f = q;
RS
98
young
G1 -- Generations
eden regions
survivor regions
old regions
eden
survivor survivor
old
old
old
...
Adapt number of young regions such that evacuation does not exceed tolerated pause time
If time permits, evacuate also old regions with largest amount of garbage
young
survivor survivor
old
old
old
old
...
99
2. Garbage Collection
2.1 Motivation
2.2 Basic techniques
2.2.1 Reference Counting
2.2.2 Mark & Sweep
2.2.3 Stop & Copy
2.3
Variants
2.3.1 Mark & Compact
2.3.2 Generation scavenging
2.4
2.5
Root pointers
2.5.1 Pointer tables
2.5.2 Conservative garbage collection
GC in .NET
Mark & Compact with multiple generations
1. Objects are allocated sequentially (no free list)
top
3. compact()
New objects are allocated sequentially again
A
generation 1
D
generation 0
top
generation 2
generation 1
generation 0
top
101
GC in .NET
generation 2
generation 1
generation 0
top
102