- Authors

- Name
- Youngju Kim
- @fjvbn20031
Runtime Environments - Stack, Heap, and Garbage Collection
When generating code, a compiler must understand how a program uses memory at runtime. This article covers memory organization, stack allocation, heap management, and garbage collection.
1. Storage Organization
The memory of a running program is typically organized as follows:
High Address
+------------------+
| Stack | <- Function call info, local variables
| | |
| v |
| |
| ^ |
| | |
| Heap | <- Dynamically allocated data
+------------------+
| Static Data | <- Global variables, constants
+------------------+
| Code | <- Program instructions
+------------------+
Low Address
Role of Each Region
- Code region: Stores compiled machine instructions. Typically read-only.
- Static data region: Stores global variables and constants. Allocated at program start and maintained until termination.
- Stack region: Activation records are pushed on function call and popped on return.
- Heap region: Stores data dynamically allocated via
malloc,new, etc.
2. Activation Records
An activation record or stack frame is created each time a function is called.
Structure of an Activation Record
High Address
+---------------------------+
| Actual parameters (args) |
+---------------------------+
| Return value |
+---------------------------+
| Control link | <- Caller's frame pointer
+---------------------------+
| Access link | <- For static scope support
+---------------------------+
| Saved registers |
+---------------------------+
| Local variables |
+---------------------------+
| Temporary variables |
+---------------------------+
Low Address
Role of Each Field
- Actual parameters: Argument values passed by the caller.
- Return value: Space to store the function's result. Registers may be used instead.
- Control Link: Pointer to the caller's activation record. Used to restore the stack on function return.
- Access Link: Pointer for accessing non-local variables.
- Saved registers: Register values that must be preserved upon function entry.
- Local variables: Variables declared within the function.
- Temporary variables: Temporary values generated by the compiler.
3. Calling Sequences
The code executed during function call and return is called the calling sequence.
What the Caller Does
1. Evaluate actual parameters and push onto the stack
2. Save the return address
3. Transfer control to the callee (call instruction)
What the Callee Does
1. Push registers that need saving onto the stack
2. Save previous frame pointer (FP) (control link)
3. Set new frame pointer (FP)
4. Allocate space for local variables (move SP)
What the Callee Does on Return
1. Store return value in designated location
2. Restore SP to FP
3. Restore saved registers
4. Restore previous FP
5. Jump to return address
Calling Sequence Example
void main() { void foo(int x) {
foo(10); int y = x + 1;
// ... bar(y);
} }
void bar(int a) {
int b = a * 2;
}
Stack changes:
[1] When main called [2] When foo(10) called [3] When bar(11) called
+-------+ +-------+ +-------+
| main | | main | | main |
+-------+ +-------+ +-------+
| foo | | foo |
| x=10 | | x=10 |
| y=11 | | y=11 |
+-------+ +-------+
| bar |
| a=11 |
| b=22 |
+-------+
4. Non-Local Variable Access
In languages with nested functions, there are times when variables of enclosing functions need to be accessed.
Access Links
An access link points to the activation record of the statically enclosing function.
// Pascal-like code
procedure A;
var x: integer;
procedure B;
var y: integer;
procedure C;
begin
x := y + 1; // Access A's x and B's y
end;
begin
C;
end;
begin
B;
end;
Stack (calls: A -> B -> C):
+-------+
| A | <- Access link: none (outermost)
| x |
+-------+
| B | <- Access link: points to A's record
| y |
+-------+
| C | <- Access link: points to B's record
| |
+-------+
C accessing x: follow access links 2 times (C -> B -> A)
C accessing y: follow access links 1 time (C -> B)
The number of access links followed equals the difference in nesting depth.
Displays
A display maintains a pointer array to the current activation record at each nesting depth, instead of following access link chains.
Display array:
display[1] = pointer to A's activation record
display[2] = pointer to B's activation record
display[3] = pointer to C's activation record
C accessing x: direct access via display[1] (O(1))
C accessing y: direct access via display[2] (O(1))
The advantage of displays is that non-local variable access is always in constant time. However, there is a cost for updating the display on function entry/exit.
5. Heap Management
The heap stores data that is dynamically allocated and deallocated.
When Heap Allocation Is Needed
- When the size of an object is unknown at compile time
- When an object's lifetime exceeds the function call scope
- Dynamic creation of data structures (linked lists, trees, etc.)
Memory Allocation Strategies
1. First Fit:
Use the first free block that is large enough
[In use][ Free block ][In use][ Free block ]
^
Allocate here
2. Best Fit:
Use the free block closest in size to the request
-> Higher possibility of external fragmentation
3. Buddy System:
Split/merge blocks of power-of-2 sizes
-> Possible internal fragmentation but fast merging
Fragmentation
External Fragmentation:
Total free space is sufficient, but no contiguous block is large enough
[Used][Free][Used][Free][Used][Free]
^^^ ^^^ ^^^
100B each, but 200B request cannot be fulfilled
Internal Fragmentation:
Unused space within an allocated block
Request: 100B, Allocated: 128B -> 28B wasted
6. Garbage Collection (GC) Basics
Garbage collection automatically reclaims memory that is no longer in use.
What Is Garbage?
Objects unreachable from the root set are called garbage.
Root set: stack variables, global variables, registers
[Root] --> [Object A] --> [Object B]
|
v
[Object C]
[Object D] --> [Object E] <- Unreachable = Garbage!
7. Reference Counting
Maintains a reference count for each object; when the count reaches 0, the object is reclaimed.
a = new Object(); // Object refcount = 1
b = a; // Object refcount = 2
a = null; // Object refcount = 1
b = null; // Object refcount = 0 -> Reclaim!
Pros and Cons of Reference Counting
Pros:
- Immediate reclamation: memory returned as soon as reference hits 0
- No pauses: no GC stop-the-world
- Simple implementation
Cons:
- Cannot handle circular references:
[A] --> [B]
^ |
+-------+
A.refcount = 1, B.refcount = 1 (never reaches 0)
- Count update overhead: count updated on every assignment
- Cache efficiency degradation: remote memory access on reference updates
8. Tracing-Based Garbage Collection
Mark-and-Sweep
Operates in two phases:
Mark Phase:
1. Start from root set
2. Mark all reachable objects
3. Traverse object graph using DFS or BFS
Sweep Phase:
1. Traverse all objects in the heap
2. Reclaim unmarked objects
3. Clear marks on marked objects
Before marking:
[A*] -> [B*] -> [C*]
|
v
[D ] -> [E ] [F*]
* = reachable from root
After sweeping:
[A] -> [B] -> [C]
|
v
[Free] -> [Free] [F]
D, E are reclaimed
Characteristics of Mark-and-Sweep
Pros:
- Correctly reclaims circular references
- Overhead proportional to the amount of garbage
Cons:
- Stop-the-World: program pauses during GC execution
- Fragmentation: memory becomes fragmented after reclamation
9. Copying Collector
Divides the heap into two semispaces.
[From Space] [To Space]
+---------+ +---------+
| A B C | copy --> | A C |
| (free) D| | (free) |
+---------+ +---------+
Only live objects are copied to the To space
-> No fragmentation, contiguous memory
Cheney's Algorithm
Copies live objects using a BFS approach.
1. Set scan and free pointers at the start of To space
2. Copy objects referenced from roots to To space
3. Process references of the object pointed to by scan:
- If referenced object is in From, copy to To
- Update reference to new location
4. Complete when scan meets free
Characteristics of Copying Collectors
Pros:
- No fragmentation (compaction effect)
- Very fast allocation (just pointer increment)
- Cost incurred only for live objects
Cons:
- Only half the memory is usable
- High copy cost if many objects are live
- Pointer updates required
10. Generational Garbage Collection
Generational GC is based on the observation that "most objects die young" (generational hypothesis).
Generation Divisions
+------------------+
| Old Generation | (long-lived objects)
| (Major GC target)| <- Low GC frequency
+------------------+
| |
| Young Generation | (newly created objects)
| (Minor GC target)| <- High GC frequency
| +----+ +------+ |
| |Eden| |Survivor||
| +----+ +------+ |
+------------------+
How It Works
1. New objects are allocated in the Young generation
2. When Young generation is full, Minor GC runs:
- Copies live objects to Survivor area
- Objects surviving multiple collections are promoted to Old generation
3. When Old generation is full, Major GC runs:
- GC over the entire heap (expensive)
Remembered Sets
References from the Old generation to the Young generation must be tracked.
If object A in Old generation references object B in Young generation:
-> Add A to the remembered set
-> Treat remembered set objects as roots during Minor GC
This allows Minor GC to avoid scanning the entire Old generation.
Real GC Implementation Examples
Java HotSpot JVM:
Young: Eden + Survivor0 + Survivor1 (copying collection)
Old: Mark-Compact or CMS/G1/ZGC
Go:
Concurrent mark-and-sweep without generations
Uses tri-color marking
Python:
Reference counting + generational GC (for circular reference handling)
11. Modern GC Advances
Concurrent GC
Performs GC concurrently with program execution.
[Program threads] -------run-------run-------run------->
[GC threads] --mark--|-sweep--|---------|-mark--|-->
-> Minimizes Stop-the-World time
Incremental GC
Divides GC work into small units and interleaves with program execution.
[Program] [GC] [Program] [GC] [Program] [GC] [Program]
10ms 2ms 10ms 2ms 10ms 2ms 10ms
-> Multiple short pauses instead of one long pause
Modern Collectors
Java G1 (Garbage First):
- Divides heap into equal-sized regions
- Collects regions with the most garbage first
- Configurable pause time target
Java ZGC / Shenandoah:
- Very short pauses (within a few milliseconds)
- Supports large heaps (multiple terabytes)
- Concurrent compaction
Summary
| Concept | Key Content |
|---|---|
| Activation Record | Stack frame created on function call |
| Control Link | Pointer to caller's frame |
| Access Link | Pointer to enclosing function for static scope |
| Display | Pointer array to current activation record per nesting depth |
| Heap Management | Dynamic allocation/deallocation, fragmentation management |
| Reference Counting | Counts references, reclaims at 0, circular reference problem |
| Mark-and-Sweep | Marks reachable objects, reclaims the rest |
| Copying Collector | Copies only live objects to new space |
| Generational GC | Divides by object lifetime for efficient collection |
Understanding runtime environments is essential for generating efficient code. The compiler phases covered in this series (lexical analysis, syntax analysis, semantic analysis, intermediate code generation, runtime environments) are the foundational technologies behind modern programming languages and tools.