🧠 OCaml Memory Model Visualizer

Understand how OCaml represents values in memory β€” boxed vs unboxed, stack vs heap, and garbage collection

Value Representations
Interactive Sandbox
GC Simulator
Cheat Sheet

How OCaml Stores Values

OCaml uses a uniform representation: every value is either an unboxed immediate (fits in a machine word) or a boxed pointer to a heap-allocated block.

Integers Unboxed

let x = 42
Stack: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ x = 85 (42<<1|1) β”‚ ← tagged: n*2+1 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ No heap allocation! Tag bit 0 = 1 β†’ immediate

Booleans Unboxed

let b = true (* = 1 *) let b = false (* = 0 *)
Stack: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ true = 3 β”‚ ← 1<<1|1 β”‚ false = 1 β”‚ ← 0<<1|1 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Unit & Chars Unboxed

let u = () (* = 0 *) let c = 'A' (* = 65 *)
Stack: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ () = 1 (0<<1|1) β”‚ β”‚ 'A' = 131 (65<<1|1)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Floats Boxed Heap

let f = 3.14
Stack: Heap: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ f = ptr ─┼───→│ hdr: Double_tag β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ 3.14 (64-bit) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Strings Boxed Heap

let s = "hello"
Stack: Heap: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ s = ptr ─┼───→│ hdr: String_tag β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ size: 5 β”‚ β”‚ 'h''e''l''l''o' β”‚ β”‚ padding + last=3 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tuples Boxed Heap

let t = (1, "hi", 3.0)
Stack: Heap: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ t = ptr ─┼───→│ hdr: tag=0 sz=3 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ field0: 3 (int 1)β”‚ β”‚ field1: ptrβ†’"hi" β”‚ β”‚ field2: ptrβ†’3.0 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Lists Boxed Heap

let l = [1; 2; 3]
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚tag=0 β”‚ β”‚tag=0 β”‚ β”‚tag=0 β”‚ β”‚hd: 3 β”œβ†’ β”‚hd: 5 β”œβ†’ β”‚hd: 7 β”œβ†’ 0 (nil) β”‚tl: ptr β”‚ β”‚tl: ptr β”‚ β”‚tl: ptr β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ [] = immediate 0 (no allocation)

Variants Unboxed/Boxed

type t = A | B of int | C of int * string
A → immediate 0 (no payload) B 5 → heap block tag=0, field=11 C (1,"x") → heap block tag=1 field0=3, field1=ptr→"x" Constant ctors: numbered 0,1,2... Non-const ctors: tagged 0,1,2...

Records Boxed Heap

type point = { x: int; y: int } let p = { x = 10; y = 20 }
Stack: Heap: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ p = ptr ─┼───→│ hdr: tag=0 sz=2 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ field0: 21 (x=10)β”‚ β”‚ field1: 41 (y=20)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Same layout as a tuple!

Arrays Boxed Heap

let a = [| 10; 20; 30 |] let fa = [| 1.0; 2.0 |]
Int array β†’ tag=0, unboxed ints Float array β†’ Double_array_tag stores raw 64-bit floats (no per-element boxing!) This is a key optimization.

Closures Boxed Heap

let add x = fun y -> x + y let add5 = add 5
Stack: Heap: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ add5 = ptr ─┼───→│ hdr: Closure_tagβ”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ code_ptr β†’ Ξ»y β”‚ β”‚ env: x = 11 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Captured variables stored in closure

Refs (Mutable) Boxed Heap

let r = ref 42 r := 99
Stack: Heap: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ r = ptr ─┼───→│ hdr: tag=0 sz=1 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ field0: 85β†’199 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ref is just a 1-field mutable record {mutable contents: 'a}

Memory Layout Explorer

Type an OCaml expression and see its memory representation.

Enter an expression above and click Visualize...

Presets

Garbage Collection Simulator

OCaml uses a generational GC with a minor heap (copying) and major heap (mark-sweep-compact). Watch it in action!

Minor Heap (nursery)

Major Heap

Free Minor (young) Major (old) Root Garbage Promoted
Allocations: 0 | Minor GCs: 0 | Major GCs: 0 | Live objects: 0 | Promoted: 0
GC Simulator ready. Click "Allocate" to create objects.

OCaml Memory Cheat Sheet

Value Representation Rules

TypeRepresentationSize (words)Notes
intImmediate (tagged)0 (in-register)63-bit on 64-bit systems, tag bit stolen
boolImmediate0false=0, true=1 (tagged)
charImmediate0Code point as tagged int
unitImmediate0Represented as 0
floatBoxed (Double_tag)1 header + 1 data64-bit IEEE 754
stringBoxed (String_tag)1 header + ⌈len/8βŒ‰Includes padding byte
'a * 'bBoxed block (tag 0)1 header + n fieldsSame as records
'a listCons cells (tag 0)1 header + 2 per cons[] is immediate 0
'a optionNone=immediate 0Some: 1 header + 1None costs nothing
'a arrayBoxed block (tag 0)1 header + n elemsMutable, bounds-checked
float arrayFlat (Double_array)1 header + nΓ—1Unboxed floats! Fast
'a ref1-field mutable block1 header + 1{mutable contents: 'a}
closureClosure_tag1 hdr + 1 code + envEnv = captured vars
Constant variantImmediate0Numbered 0,1,2...
Non-const variantBoxed block1 header + n fieldsTag = constructor index

GC Quick Facts

PropertyMinor HeapMajor Heap
StrategyCopying (semi-space)Mark-sweep-compact
Default size256 KBGrows as needed
SpeedVery fast (Β΅s)Slower (ms)
When triggeredMinor heap fullMajor heap grows past threshold
Objects survivePromoted to majorStay until unreachable
AllocationBump pointer (fast!)Free-list based

Performance Tips

TipWhy
Use int over float when possibleUnboxed = no allocation
Use float array for numeric workFlat layout, no per-element boxing
Prefer Array over List for random accessContiguous memory, O(1) access
Avoid excessive ref usageEach ref = heap allocation
Use [@unboxed] for single-field recordsEliminates wrapper allocation
Reuse buffers with Buffer.tAvoids repeated string allocations
Profile with Gc.stat()Shows allocation rates and GC frequency