Skip to content

Commit

Permalink
Merge pull request #24 from SquareBracketAssociates/kdh-review-chapter-9
Browse files Browse the repository at this point in the history
Suggested improvements for ”The Spur Memory Manager Overview”
  • Loading branch information
guillep authored Jan 6, 2025
2 parents 164adc9 + 0a9b537 commit d3d6a13
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 64 deletions.
24 changes: 8 additions & 16 deletions Part3-MemoryManagement/GarbageCollector/freeList.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
### The Free List


When a new object should be allocated, the VM checks first if there is an object that was already allocated and garbage collection of the given size that can be reused. Else it allocates a new object by "cutting" and splitting a large free object.
The management of such used _cells_ is managed by the _Free List_ and its companion the _Free Table_.
When a new object should be allocated, the VM checks first if there is an object that was already allocated and garbage collected of the given size that can be reused. Else it allocates a new object by "cutting" and splitting a large free object.
The management of such used _cells_ is managed by the _Free List_ and its companion the _Free Table_.
The Free List manages chunks of memory below numFreeLists \(i.e., 63 in 64 bits architecture\).
The Free Table manages larger chunks of memory.

Expand All @@ -16,8 +16,8 @@ It refers to free cells \(which are special object tagged as Free class\).
The minimum size of a free cell is two words: one for the object header and the next.
Such minimal cells are used to allocate an object with one single slots \(because one slot is for the header and the second one is for the slot\).

The free cells stays in the memory where they are allocated as shown in Figure *@freeListMemory@*.
In Figure *@freeListMemory@*, there are two objects of size 5 word is free. Such objects are structured in a linked-list of objects of the same size. A free cell is an element of a linked-list.
The free cells stay in the memory where they are allocated as shown in Figure *@freeListMemory@*.
In Figure *@freeListMemory@*, there are two objects of size 5 that are free. Such objects are structured in a linked list of objects of the same size. A free cell is an element of a linked list.
The first object then points to the next free object of the same size. The second object is the last one of this size so its next slot is empty. In addition when the size allows it, the free object has a previous pointer in addition to the next one \(implementing a double linked list\).

![Free cells of size 5 in the memory.](figures/freeListMemory2.pdf width=100&label=freeListMemory)
Expand All @@ -27,23 +27,15 @@ The first object then points to the next free object of the same size. The secon


The free list is a structure that keeps tracks of free cell objects based on their size.
Figure *@freeListMemory@* shows that the free list linked-lists are built using the free objects.
Figure *@freeListMemory@* shows that the free list linked lists are built using the free objects.

On 64 bits, the free list has 63 slots to keep a free cell linked list per number of slots of the object.
The first element of the free list is a pointer to the free tree \(a tree structure that manages chunk of memory larger than 63 words\).
On 64 bits, the free list has 63 slots to keep a free cell linked list per number of slots of the object.
The first element of the free list is a pointer to the free tree \(a tree structure that manages chunks of memory larger than 63 words\).

% +Free list first view.>figures/freeListMemory2.pdf|width=100|label=freeListMemory2+

The free list first element is a pointer to the tree table, the next elements are pointers to linked-list of free objects, one linked-list per object size.
The free list first element is a pointer to the tree table, the next elements are pointers to linked-list of free objects, one linked-list per object size.

![Free list view is a table of linked-lists of free objects.](figures/freeListBasic.pdf width=100&label=freeListBasic)

### Free Table








54 changes: 27 additions & 27 deletions Part3-MemoryManagement/GarbageCollector/memoryStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The Pharo virtual machine implements an object memory manager named Spur.
An object memory manager is a memory manager whose allocation units are objects.
In contrast to the operating system memory manager that manipulates raw memory, the Spur memory manager manipulates only objects.
For example, the lowest-level allocation operation is to allocate an object specifying the desired number of slots, format and class index.
We saw in the previous chapters the object format, and what these three arguments mean.
In the previous chapters, we saw the object format and what these three arguments mean.

```smalltalk=true
memoryManager
Expand All @@ -20,24 +20,24 @@ The virtual machine tracks the life-cycle of all objects it allocates.
The memory manager implements an automatic garbage collection mechanism.
It detects when an object has no more incoming references, and deallocates it.
The garbage collector of Spur is precise and generational.
It is precise because it distinguishes non-ambiguously object pointers from random memory adresses.
It is precise because it distinguishes non-ambiguously object pointers from random memory addresses.
It is generational because it categorizes objects _by age_, treating them differently depending on their age.

In this chapter we do an overview of the Spur memory manager, and the concepts behind it.
We will study how the memory is organized, how object generations impacts this organization, and how objects grow old in this generational setup.
In this chapter we give an overview of the Spur memory manager, and the concepts behind it.
We will study how the memory is organized, how object generations impact this organization, and how objects grow old in this generational setup.

### Spur features


The Spur memory model supports the following features:
The Spur memory model supports the following features:

- Support both 32 and 64 bits.
- Performance improvement. Several decisions led to a much faster system \(new GC, large hash, immediate characters\).
- Variable sized and segmented memory. The memory allocated in the operating system by the virtual machine can grow and shrink according to the image size. Pharo images as large as several Gb are possible.
- Incremental and efficient garbage collector. As we describe in the following chapters, the GC is now.
- Fast become: the model introduces forwarders are special objects that avoid to walk the complete heap to swap references.
- Ephemerons: the model introduces advanced weak structures called _Ephemeron_. An Ephemeron is an object which refers strongly to its contents as long as the Ephemeron’s key is not garbage collected, and weakly from then on.
- Pinned objects. Pinned objects will not be moved by the garbage collector. This is an important point for Foreign Function Interface - as you can read in the corresponding book.
- Support both 32 and 64 bits.
- Performance improvement. Several decisions led to a much faster system \(new GC, large hash, immediate characters\).
- Variable sized and segmented memory. The memory allocated in the operating system by the virtual machine can grow and shrink according to the image size. Pharo images as large as several Gb are possible.
- Incremental and efficient garbage collector. As we describe in the following chapters, the GC is now.
- Fast become: the model introduces forwarders as special objects that avoid to walk the complete heap to swap references.
- Ephemerons: the model introduces an advanced weak structure called _Ephemeron_. An Ephemeron is an object which refers strongly to its contents as long as the Ephemeron’s key is not garbage collected, and weakly from then on.
- Pinned objects. Pinned objects will not be moved by the garbage collector. This is an important point for the Foreign Function Interface - as you can read in the corresponding book.

### Memory Structure Overview

Expand All @@ -48,15 +48,15 @@ The old space contains objets that did survive in the new space for some time, a

At startup, the memory manager requests the operating system a chunk of raw memory to store the new space and the old space.
The memory manager uses the first part of this memory as the new space, and the rest as old space.
Figure *@memoryMap@* depicts how the two spaces are laid-out in memory, considering that lower addresses are at the left, and higher addresses are at the right.
Figure *@memoryMap@* depicts how the two spaces are laid out in memory, considering that lower addresses are at the left, and higher addresses are at the right.

![Memory Map: a new space followed by an old space.](figures/memoryMap.pdf width=100&label=memoryMap)

Addresses in the new space are lower than those in the old space.
This way, the VM easily determines if an object is old or young by comparing its address against the limit of the new space.
The memory manager stores the limits of the new space as `newSpaceStart` and `newSpaceLimit`. It defines that an object is young if its address is less than the new space limit.
The memory manager stores the limits of the new space as `newSpaceStart` and `newSpaceLimit`. It defines that an object is young if its address is less than the new space limit. See Listing *@youngobject@*.

```smalltalk=true&caption=A young object is an object located below the newSpaceLimit
```smalltalk=true&caption=A young object is an object located below the newSpaceLimit.&anchor=youngobject
memoryManager newSpaceStart.
memoryManager newSpaceLimit.
Expand All @@ -73,7 +73,7 @@ SpurMemoryManager >> isYoung: oop

The new space remains fixed once initialized i.e., it does not grow after its allocation.
On the contrary, the old space is organized in one or more memory segments, and it can grow dynamically by adding new segments to it.
The new space and first segment of the old space are allocated in single contiguous chunk of memory as we have seen above.
As we have seen above, the new space and the first segment of the old space are allocated in a single contiguous chunk of memory.
Newly added segments do not require to be contiguous, but they need to be at higher addresses than the first segment.

When a new segment is added, a bridge is added to the end of its previous segment.
Expand All @@ -94,34 +94,34 @@ The size of this first segment is computed as the addition of the image size and
### Spur Generational Garbage Collection


Spur implements a generational automatic garbage collector based on an heuristic named the generational hypothesis.
The generational hypothesis states that most objects die young, specially true in highly-interactive applications, so young objects are stored separately than old objects.
Spur implements a generational automatic garbage collector based on an heuristic named the _generational hypothesis_.
The generational hypothesis states that most objects die young, especially true in highly-interactive applications, so young objects are stored separately from old objects.
This is why the Pharo VM uses two different garbage collector algorithms: one for new objects implementing a generation scavenger, and one for old objects implementing a mark and compact.

The memory manager allocates by default objects in the new space.
By default, the memory manager allocates objects in the new space.
When the new space has little space left, it is garbage collected using a copy collection algorithm named generation scavenger, that we will explore in detail in a following chapter.
The new space is much smaller than the old space, so garbage collecting it is fast, producing unnoticeable application pauses.
If the generational hypothesis holds, unused young objects are reclaimed shortly after their instantiation and never moved to the old space.

Objects that are not reclaimed during a garbage collection are called _survivors_.
As objects survive several new space garbage collections they grow old.
Eventually, objects old-enough are _tenured_: they are moved to the old space.
As objects survive several new space garbage collections, they grow old.
Eventually, objects old enough are _tenured_: they are moved to the old space.
The old space is several times bigger than the new space, thus garbage collecting it is expensive and creates long application pauses.
Most objects are collected during new space collections, so collect the old space is not often required.
Most objects are collected during new space collections, so collecting the old space is not often required.

When the old space has little space left, a mark and compact collection algorithm reclaims unused objects. This algorithm first marks all used objects, and then scans the entire old space freeing unmarked objects and compacting the memory.

### The Stack

The stack \(both Pharo and C\) resides in the lower address of the memory.
This is the stack used by the C code and also the stack pages are allocated in this stack.
This is the stack used by the C code and also the stack fames are allocated in this stack.
All the execution of a process stores the information in the stack.
The stack is the real representation of the contexts in the image.
The frames are in a sequence in the stack.
Each frame knows the calling frame with a pointer.
The stack is the real representation of the contexts in the image.
The frames are in a sequence in the stack.
Each frame knows the calling frame with a pointer.
Objects referenced into stack frames are retained i.e., never garbage collected.

### Conclusion


We sketch a first overview of the memory architecture of Pharo.
We sketched a first overview of the memory architecture of Pharo.
42 changes: 21 additions & 21 deletions Part3-MemoryManagement/GarbageCollector/newSpace.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
### The New Space


The new space is the region where young objects are allocated.
It is the place where the generational garbage collector is taking place.
it is commonly named a _scavenger_.
The new space is the region where young objects are allocated.
It is the place where the generational garbage collection is taking place.
It is commonly named a _scavenger_.

#### Minimal objects: Liliputians

Expand All @@ -17,30 +17,30 @@ This minimum size for any object is important for some features of the VM \(GC,

### New Space Memory Layout

The new space is divided into three areas \(eden, future and past\) as shown by Figure *@newSpace@*.
The new space is divided into three areas \(Eden, Future and Past\), as shown by Figure *@newSpace@*.
The Eden \(5/7 of the new space\) and two other areas \(1/7 of the new space each\) that alternatively play the role of _past_ and _future_ space.
The VM always allocates new objects into the Eden space if there is enough space.

![The NewSpace structure composed of the eden, and two past and future regions.](figures/newSpace.pdf width=60&label=newSpace)
![The NewSpace structure composed of the Eden, and two Past and Future regions.](figures/newSpace.pdf width=60&label=newSpace)

### The Scavenger


The scavenger is a copy-collector responsible to manage the new space memory region.
The scavenger is periodically triggered on some events such as: the eden is full i.e., left space reached a predefined threshold. There are multiple scavenger policies:
- TenureByAge: it is the default policy. It consists in copying surviving objects either in future space or old space depending on a threshold \(cf `SpurGenerationScavenger>>shouldBeTenured:`\). Surviving objects in past space with addresses below this threshold are tenured i.e., copied to the old space instead of future space. Initially, the threshold value is 0 meaning that the scavenger will not tenure any surviving objects, they are copied to future space. At the end of the scavenge, the scavenger updates the treshold if the future space is filled at more than 90%. The next scavenge will then tenure objects.
The scavenger is a copy collector responsible for managing the new space memory region.
The scavenger is periodically triggered on some events such as: the Eden is full i.e., remaining space reached a predefined threshold. There are multiple scavenger policies:
- TenureByAge: it is the default policy. It copies surviving objects either in future space or old space depending on a threshold \(cf `SpurGenerationScavenger>>shouldBeTenured:`\). Surviving objects in past space with addresses below this threshold are tenured i.e., copied to the old space instead of future space. Initially, the threshold value is 0 meaning that the scavenger will not tenure any surviving objects, they are copied to future space. At the end of the scavenge, the scavenger updates the treshold if the future space is filled at more than 90%. The next scavenge will then tenure objects.
- TenureByClass: this policy consists in tenuring objects instance of a specific class.
- TenureToShrinkRT: this policy consists in tenuring objects to shrink the remember table i.e., minimizing objects in the old space that reference objects in the new space.
- DontTenure: this policy consists in not tenuring any objects i.e., threshold is fixed at 0.
- MarkOnTenure: the full mark and sweep GC of the old space calls the scavenger with this policy. The threshold will be 0.
- MarkOnTenure: the full mark and compact GC of the old space calls the scavenger with this policy. The threshold will be 0.


The VM allocates new objects in the eden space.
When a newly allocated object address in the eden space reaches the scavenge threshold, the scavenger is triggered.
To free some space in the eden, the scavenger does not iterate over all objects it contains.
The VM allocates new objects in the Eden space.
When a newly allocated object address in the Eden space reaches the scavenge threshold, the scavenger is triggered.
To free some space in the Eden, the scavenger does not iterate over all objects it contains.
It computes surviving objects i.e., referenced by **root objects**.
There are three kind of roots:
- objects referenced in the stack i.e., used as receivers or parameters;
- objects referenced in the stack i.e., used as receivers or arguments;
- objects stored in the remembered set. This set contains all objects allocated in the old space that contains at least one reference to an object in the new space;
- special objects known by the VM such as: nil, true, false, class table, etc.

Expand All @@ -49,11 +49,11 @@ There are three kind of roots:

The scavenger starts by copying roots references allocated into Eden or past spaces into the future space.
Then, it traverses these copied objects and copies their referenced objects that reside into Eden or past space into the future space.
At the same time, traversed references are fixed to correctly reference the copied objects.
At the same time, traversed references are fixed to correctly reference the copied objects.
Similarly, root objects references are also updated.
Finally, the future space contains all reachable objects from roots that were present into the Eden and past spaces.
Moreover, all their references have been updated to correctly point to objects into the future space.
If the future space is filled during the scavenge, some objects are tenured i.e., copied into the old space.
Moreover, all their references are updated to correctly point to objects into the future space.
If the future space is filled during the scavenge, some objects are tenured i.e., copied into the old space.

There are multiple strategies regarding the tenuring of objects:
- By age, using the addresses in the past \(older objects have smaller addresses\).
Expand All @@ -66,7 +66,7 @@ Once the scavenge is finished, the future and past spaces are switched; it just
!!note Add a figure showing the switch


### Example of a Scavenger pass
### Example of a Scavenger pass


```
Expand All @@ -87,28 +87,28 @@ future space: A, C
```


- Step 2: We go over first surviving object \(A\)
- Step 2: go over the first surviving object \(A\)

```
future space: A, C, B
(C was already copied, so we just update the reference)
```

- Step 3: We go over second surviving object \(C\)
- Step 3: go over the second surviving object \(C\)

```
future space: A, C, B
(C points to A, but A was already copied, so we just update the reference)
```


- Step 4: We go over next surviving object \(B\)
- Step 4: go over the next surviving object \(B\)

```
future space: A, C, B, D
```

- Step 5: We go over next surviving object \(D\)
- Step 5: go over the next surviving object \(D\)

```
Nothing to do, and nothing new in future space
Expand Down

0 comments on commit d3d6a13

Please sign in to comment.