Skip to content

How is Pharo memory structured when an image is open ?

Inao edited this page Jun 14, 2023 · 1 revision
flowchart TB
	subgraph OS["Old Space (GB)"]
	S1 --> S2 --> S3
	end
	

	subgraph NS["New space (MB)"]
    direction RL
	C["Past (1/7th) "]
	D["Future (1/7th)"]
	E["Eden/Nursery 5/7th of new space"]
	end
	
	RS["Remembered Set"]
	OS -.-> RS

Loading

Memory is split into two main parts, the new space containing newer object, whose size is in MB, and the old space (size in GB) which contains objects that have survived several garbage collection iterations.

The new space has lower addresses than the old space segments. This makes it easy to know if a reference points to a new object or not.

flowchart LR
A["New space (MB)"]    
B["1st Segment"]
C["2nd Segment"]
D["3rd Segment"]
A -."NO gap".-> B -."gap".-> C-."gap".-> D
Loading

New space structure

The new space is split into three subparts : - The Eden or Nursery where brand new objects are created. Many objects have a very short lifespan, so the Eden is the largest part of the new space - The Past contains objects that have survived at least one garbage collection but have not been promoted to the old space. - The Future, which will be used on garbage collection to store surviving objects for the next cycle.

Old Space Structure

The old space is made of segments because the OS might not give continuous memory to Pharo when it is asking for more. However, we can make sure that the new segments have higher addresses than the old ones, which allows for bridges

In Pharo, the link between segments is done by bridges which are faked objects. Bridges have their header at the very end of one segment and they have an apparent size, such that they end at the beginning of the next segment. The bridges are opaques. Nobody should try to read them, they do not contain anything and if accessing their apparent memory area, Pharo would try to use memory that is not allocated to it.

A bridge is also a pinned object, meaning that the Pharo VM should not try to move it. This ensures that it remains at the right place to "bridge the gap" between two segments.

Quote on bridges from the SpurMemoryManager class comment :

Segmented Old Space via Bridges A segmented oldSpace is useful. It allows growing oldSpace incrementally, adding a segment at a time, and freeing empty segments. But such a scheme is likely to introduce complexity in object enumeration, and compaction (enumeration would apear to require visiting each segment, compaction must be wthin a segment, etc). Spur turns segmented old space into one that appears to be a single contiguous space by using fake pinned objects to bridge the gaps between segments. We call these "bridges". The last two words of each segment can be used to hold the header of a pinned object whose overflow size is the distance to the next segment. The pinned object's classIndex is one of the puns so that it doesn't show up in allInstances or allObjects; this can perhaps also indicate to the incremental collector that it is not to reclaim the object, etc. Bridge objects need a large enough overflow size field to stretch across large gaps in the address space. This is no problem in the 32-bit system which has a 32-bit overflow slot coult, 4 times larger than necessary. But the 64-bit sytsem only has a 56 bit overflow slot count, 5 bits short of spanning a full 64-bit address space. We expect that this is adequate if we preferrentially allocate segments at one end of the address space or the other (in fact we try and allocate low).

Clone this wiki locally