Memory and runtime

How a language reclaims memory and how it reaches the processor are the two axes with the most direct effect on deployment: they determine start-up time, pause behaviour, binary size, and what the program can promise about latency.

Memory management models

Five models, ordered from least to most automatic. Several languages combine two: C++ pairs manual allocation with RAII, and Python pairs reference counting with a cycle collector.

Model Mechanism Representative languages What it buys What it costs
Manual The program allocates and frees explicitly. C, Zig, assembly No runtime overhead; allocation cost is fully visible Use-after-free, double free, and leaks are program errors
RAII / scope-bound Resources are released when the owning object leaves scope. C++, Ada, Rust Deterministic release without a collector Cycles still leak unless broken explicitly
Reference counting Each object counts its live references and frees at zero. Swift, Objective-C, Python, PHP, Perl Deterministic destruction and low pause times Cycles leak; count updates cost on every copy
Tracing garbage collection A collector periodically finds and frees unreachable objects. Java, C#, Go, Haskell, JavaScript Cycles are collected; allocation is cheap Pauses and memory overhead; timing is not under program control
Ownership and borrowing A compile-time discipline proves each value has one owner and no dangling borrows. Rust Safety of a collector with the cost model of manual memory Steeper learning curve; some structures need escape hatches

Sources: language and runtime documentation. Cost columns describe documented behaviour of the reference implementation, not benchmark results.

Automatic and deferredAutomatic and promptManual and deferredManual and promptCollector decidesMixedProgrammer decidesNonePartialCompleteManual (C, Zig)Manual (C, Zig)RAII (C++, Ada)RAII (C++, Ada)Ownership (Rust)Ownership (Rust)ARC (Swift, Obj-C)ARC (Swift, Obj-C)Refcount + cycles (Python)Refcount + cycles (Python)Tracing GC (Java, Go)Tracing GC (Java, Go)Release timing is predictableFreed from manual bookkeeping
Figure 1. The two costs a memory model trades against each other. Nothing sits in the bottom-left quadrant, because a model that gives neither predictable release nor freedom from manual bookkeeping has no reason to exist. Positions are ordinal readings of the model, not measurements of any implementation.

Back to top

Reference cycles

Strong cycle: leaks Person Home strong strong Neither count reaches 0 One weak edge: reclaimed Person Home weak strong Home reaches 0, then Person
Figure 1. A strong reference cycle under reference counting, and the same graph with one edge declared weak. Reference counting cannot reclaim the left case; a tracing collector can. Diagram: Programming Language Atlas.

The defining limitation of counting

Reference counting reclaims an object the moment its last reference disappears, which makes destruction deterministic and pauses short. It cannot, however, reclaim a group of objects that reference each other, because every count in the group stays above zero.

Languages that count therefore give the programmer a way to declare an edge non-owning. Swift provides weak and unowned; Objective-C provides the same pair; Python and PHP instead run a secondary cycle collector so that the programmer need not annotate.

Tracing collectors have the opposite profile: cycles are free, but destruction time is decided by the collector rather than the program. That is why latency-sensitive systems either avoid allocation on the hot path or choose a counted or owned model.

See Memory management in the Swift article for a worked example of a cycle and its resolution.

Back to top

Execution models

How source reaches the processor. The axis records the route taken by the reference implementation; alternative implementations are noted in each language entry.

Model Mechanism Representative languages What it buys What it costs
Ahead-of-time compilation Source is translated to machine code before deployment. C, C++, Rust, Go, Swift, Haskell Fast start-up, no runtime compiler, small deployable surface Per-target builds; no runtime specialisation
Bytecode with JIT Source compiles to a portable bytecode that a runtime compiles again while running. Java, C#, Kotlin, Scala, JavaScript Portable artefacts; profile-guided specialisation at runtime Warm-up period and a large runtime to ship
Bytecode interpretation A virtual machine executes bytecode without translating it to machine code. CPython, Ruby, Lua, PHP Simple, portable, and quick to start Interpretation overhead per instruction
Transpilation Source is translated to another high-level language and executed by that language's toolchain. TypeScript, Elm, ClojureScript Reuses a mature host platform and its ecosystem Debugging crosses a translation boundary; host semantics leak through
Query planning A declarative statement is compiled into an execution plan chosen by a cost model. SQL, Datalog The engine may re-plan as data changes Performance depends on statistics the author does not control

A single language may appear under more than one model across implementations: Python is interpreted by CPython and JIT-compiled by PyPy; Kotlin targets both the JVM and native code.

Dispatchchoosing which code runs for a callRepresentationhow a value is laid out and how large it isAllocationwhere a value lives and who reclaims itSchedulingwhich thread or task makes progress next
Figure 3. What every execution model has to provide, whoever provides it. A model does not remove these layers, it relocates them: an ahead-of-time compiler resolves the top two before shipping, a JIT resolves them while running, and an interpreter resolves them at every step.

Back to top

Deployment consequences

Start-up latency

AOT binaries start in milliseconds. JIT runtimes need a warm-up period before reaching peak throughput, which matters most for short-lived processes.

Tail latency

Tracing collectors introduce pauses whose length depends on live-set size. Counted and owned models trade that for steady per-operation cost.

Artefact size

A managed language ships its runtime; an AOT language ships a self-contained binary but one build per target platform.

Interoperability

Languages with a C-compatible ABI can be called from almost anywhere; languages with a managed runtime interoperate best within their own platform.

Back to top