Objective. This note compares storage organization across C++23, the Java Virtual Machine (JVMS, Java SE 25), and the Ethereum Virtual Machine (EVM). These systems expose different abstraction levels: C++ specifies object lifetimes and storage-duration categories; the JVM specifies run-time data areas for a managed abstract machine; and the EVM specifies consensus-critical execution state, persistent account storage, and gas-priced computation.
A comparative taxonomy, not a claim of equivalence
Method. The comparison projects specification-defined or documentation-defined constructs onto five analytical layers—executable representation, long-lived state, activation or operand state, runtime-allocated working data, and execution context or I/O—and evaluates each layer by purpose, lifetime, management authority, and cost model. Normative claims, implementation examples, and fork-dependent protocol facts are explicitly separated.
Contribution and limitation. The taxonomy is intended for teaching, review, and transfer of intuition between runtimes. It is not a formal semantics, a performance model, or a promise that similarly placed constructs are architecturally identical. Where the analogy leaks, the note states the leak rather than hiding it.
Contribution and limitation. The taxonomy is intended for explanation, review, and transfer of intuition between runtimes. It is not a formal semantics, performance model, or proof that similarly placed constructs have identical behavior. Where the analogy fails, the failure is stated explicitly in §8.
Keywords: runtime data areas; storage duration; managed runtime; JVM; EVM; persistent state; comparative systems pedagogy.
Normative level
C++ storage durations, JVMS run-time data areas, and consensus-level EVM behavior are treated as the primary source of truth.
Implementation level
ELF/PE sections, HotSpot Metaspace, JIT code caches, and compiler placement decisions are labeled as implementation choices rather than language guarantees.
Pedagogical level
The five layers are an explanatory projection. They support comparison, but they do not erase differences in lifetime, observability, determinism, or cost.
§1 · Motivation
Three specification layers for “a running program”
- C++ specifies an abstract machine, object lifetimes, and four storage-duration categories. Conventional implementations map these semantics onto an operating-system process, ABI, loader, registers, stacks, and dynamically allocated regions; those concrete regions are not mandated by the language standard [1].
- JVM specifies a managed abstract machine with shared and per-thread run-time data areas. HotSpot details such as Metaspace and a code cache are implementation choices layered beneath the JVMS abstraction [2].
- EVM specifies consensus-critical execution. Each execution frame has stack, memory, code, and input/output context, while contract storage contributes to persistent protocol state; gas makes resource use explicit and fork-dependent [5].
The systems therefore cannot be compared by names alone. “Stack,” “memory,” “static,” and “storage” denote different mechanisms. A useful comparison must ask the same questions at each level: what a region is for, how long its contents survive, who manages it, and what costs or constraints govern it.
Accuracy discipline
The comparison uses the narrowest accurate term available: storage duration for C++, run-time data areas for the JVM, data locations and execution-frame state for Solidity/EVM, and gas for the EVM cost model. Broader words such as “memory,” “heap,” and “stack” are interpreted only after the runtime context is known.
§2 · The framework
P · L · M · C
For any variable or storage construct, ask four questions before naming the area: Purpose — what is it for? Lifetime — how long can it live? Management — who allocates or releases it? Cost — what resource makes it expensive?
The five-layer lens
Layer naming policy
The final edition uses deliberately abstract layer names: Executable Representation, Long-Lived State, Activation / Operand State, Runtime-Allocated Working Data, and Execution Context & I/O. Short labels such as “code,” “state,” “stack,” “dynamic,” and “runtime” appear only as interface cues. They are not the formal layer names.
For comparative purposes, the note projects each runtime onto five analytical layers and reads each layer along four axes: purpose (what it represents), lifetime (frame, thread, process/VM, transaction, or cross-transaction), management (language, runtime, programmer, or protocol), and cost (machine resources, runtime overhead, or gas). These layers are categories of analysis, not claims that the underlying mechanisms are interchangeable.
Reader warning — Layer 2 is not "persistent" everywhere
In C++ and the JVM, long-lived language/runtime state is normally bounded by process, class,
class-loader, or VM lifetime. In the EVM, persistent storage is committed
to protocol state and survives across transactions until later execution changes it. “Persistent”
therefore means cross-transaction and mutable, not immutable or literally permanent.
Hover or focus a layer — the same layer lights up across all three runtimes. Tab to step through.
● C / C++
native process address space
● JVM / Java
JVMS run-time data areas
● EVM / Solidity
deterministic on-chain machine
§3 · C++23
Language-level storage duration and common process realizations
The C++ standard specifies object lifetime and storage duration, not a required process layout. It distinguishes static, thread, automatic, and dynamic storage duration [1]. Text, data, BSS, TLS, call stacks, registers, and allocator “heaps” are conventional ABI and operating-system realizations of those semantics.
Normative claim vs common realization
“Automatic” does not mean “must be on the stack,” and “dynamic” does not mean “must be in a region named the heap.” Optimizing implementations may keep values in registers, scalar-replace objects, merge constants, or eliminate storage entirely, provided observable C++ semantics are preserved.
storage_duration.cpp
int a = 10; // static storage duration; commonly writable data
int b; // static duration; zero-initialization is guaranteed
int c = 0; // static duration; commonly placed with zero-initialized data
const int D = 42; // static duration; may be read-only or require no storage
static int e = 5; // static duration with internal linkage
thread_local int t;// thread storage duration; commonly thread-local storage
int main() {
int x = 20; // automatic duration; often a register or stack slot
int* p = new int(99); // p: automatic duration; *p: dynamic storage duration
delete p; // ends the allocated object's lifetime and releases storage
}
Indeterminate values
The zero-initialization guarantee above applies to static- and thread-storage initialization.
A local int b; with automatic storage duration has an indeterminate value;
evaluating that value is generally undefined behavior in C++23.
What is distinctive
- Native machine code is the dominant implementation strategy, but the language is specified by an abstract machine rather than a particular executable-file format.
- Automatic and member-object lifetimes support deterministic destruction; RAII and ownership types
can manage resources without explicit
deleteat each use site. - Dynamic storage is managed explicitly or through libraries such as smart pointers and containers; the standard does not require a tracing garbage collector.
- Raw pointers and pointer arithmetic expose low-level addressing with correspondingly strong safety obligations.
- Undefined behavior permits optimizations that assume prohibited executions do not occur.
§4 · JVM, Java SE 25
Specification-level run-time data areas and implementation choices
The JVMS defines logical run-time data areas independently of any particular operating-system layout.
Some are shared by all threads and live for the JVM instance; others are created per thread. The core
areas are the pc register, JVM stacks, heap, method area, run-time constant
pools, and native method stacks [2].
Two levels must remain separate
The JVMS level describes frames, local-variable arrays, operand stacks, the heap, and shared class structures. The implementation level decides how those abstractions map to native memory, registers, compiled code, garbage collectors, and operating-system threads.
Method area, Metaspace, and code cache
The method area is a JVMS abstraction. In HotSpot, class metadata is allocated in native memory commonly described as Metaspace; JIT-compiled machine code is held in implementation-specific code caches. Metaspace is therefore not a portable synonym for the entire method area.
Do not infer physical placement of static fields from the specification
The JVMS associates fields and methods with class structures, but it does not prescribe a physical address-space layout. In HotSpot, JDK 8 removed the permanent generation and moved class metadata into native memory commonly called Metaspace [4]. Static-field values should therefore be described portably as class-associated state; their concrete representation is an implementation detail, not a JVMS guarantee.
Student.java
class Student {
static int count = 0; // class-associated state; physical placement is JVM-specific
String name; // instance field stored as part of a heap object
void enroll(String course) {
int credits = 3; // logical local-variable slot in this frame
count++; // getstatic / putstatic at the bytecode level
Student s = new Student(); // reference local; object normally allocated on heap
}
}
// A JIT may keep locals in registers and may eliminate allocations
// when those optimizations preserve Java's observable behavior.
What is distinctive
- Class files contain bytecode and symbolic metadata; a JVM may interpret, compile, or mix execution strategies.
- Garbage collectors reclaim unreachable heap objects according to implementation policy; collection timing is not a prompt-destruction guarantee.
- References do not support language-level pointer arithmetic, and array accesses are checked.
- JVMS frames are logical structures. A JIT may represent their values in registers or optimized metadata rather than literal native stack slots.
- Class-file portability is subject to supported class-file versions and to platform-specific native libraries or external resources.
§5 · EVM
Consensus-critical frame state and persistent contract state
The EVM is a deterministic state-transition machine executed by Ethereum clients. Each execution frame has code, an operand stack, linear memory, input data, return data, gas, and environmental context; persistent account storage contributes to protocol state. All consensus implementations must reproduce the same valid transition under the active fork rules [5].
The EVM stack is an operand stack
It is not a C/C++-style call stack. Each execution frame manipulates a LIFO stack of at most 1024 256-bit words. Message calls create nested EVM frames; ordinary Solidity internal function calls are usually compiled as control flow within the current frame rather than as new EVM message-call frames.
EVM memory is frame-local linear scratch space
Memory is a byte-addressed, zero-initialized linear array that expands during execution and incurs expansion cost. It has no object-level deallocation or garbage collector. The entire memory instance is discarded when its message-call or creation frame ends.
Counter.sol
contract Counter {
uint256 public count; // persistent storage; mutable across txs
event Incremented(uint256 value);
function increment(uint256 amount) external {
// amount is encoded in calldata and loaded as required
uint256 next = count + amount; // usually stack-resident; compiler-dependent
count = next; // SSTORE under active fork gas rules
uint256[] memory buf = new uint256[](3); // frame-local linear memory
buf[0] = next; // discarded with this external call frame
emit Incremented(next); // log entry in the transaction receipt
}
}
Persistent does not mean immutable
Contract storage survives across transactions, but later execution can update or clear it. Contract code is stored separately and is not writable in place by ordinary EVM opcodes. Protocol rules for account/code lifecycle and gas are fork-dependent.
What is distinctive
- Execution is consensus-critical: valid clients must agree on the resulting state, logs, return data, and gas accounting.
- Persistent storage is word-keyed contract state; its writes are substantially more expensive than stack or memory operations, with exact costs determined by the active fork.
- Transient storage introduced by EIP-1153 is address-scoped, transaction-lifetime key-value state. It can be observed across relevant call frames during one transaction, participates in revert semantics, and is cleared at transaction end [6].
- Solidity's
storage,memory, andcalldataannotations describe source-level data locations for reference types; raw EVM execution manipulates stack words, byte offsets, and storage keys [7].
§6 · Side by side
Unified comparison with abstraction levels preserved
| Analytical layer | C++23 | JVMS 25 / HotSpot note | EVM |
|---|---|---|---|
| Executable Representation | Program image; commonly text/code sections | Method-area code structures; optional implementation code cache | Account code executed as EVM bytecode |
| Long-Lived State | Static- and thread-duration objects; process/thread bounded | Class-associated data; physical placement implementation-specific | Persistent storage across transactions; transient storage within one transaction |
| Activation / Operand State | Automatic objects and ABI call state; registers or stack are common | Per-thread frames with local variables and operand stacks | Per-frame 256-bit operand stack, depth ≤1024 |
| Runtime-Allocated Working Data | Dynamic-storage objects, commonly allocator-backed | Heap objects and arrays, collector-managed | Frame-local linear memory; no general object heap |
| Execution Context & I/O | ABI, loader, runtime library, unwinding, OS services | pc, native stacks, class loading, GC/JIT internals | Gas, calldata, returndata, logs, environment, call-frame context |
Axes that differ most
| Property | C++23 | JVM / Java | EVM / Solidity |
|---|---|---|---|
| Longest internal lifetime | Program or thread lifetime | JVM, class, or class-loader lifetime | Cross-transaction persistent state |
| Working-data management | Scope destruction plus explicit/library-managed dynamic allocation | GC-managed heap plus per-thread frames | Whole-frame memory discard; protocol-managed state transitions |
| Execution model | Abstract machine, commonly native code | Bytecode with interpreter and/or JIT implementation | Consensus stack-machine bytecode |
| Persistence beyond process | External files, databases, services | External files, databases, services | Storage committed to Ethereum protocol state |
| Determinism obligation | No consensus requirement | No consensus requirement | Consensus-critical under fork rules |
| Addressing model | Typed objects plus raw pointers | Typed references; no Java pointer arithmetic | Stack words, memory byte offsets, storage keys |
| Cost model | CPU, memory hierarchy, allocator, OS | CPU, heap, GC, compilation, native resources | Gas schedule plus protocol limits |
| Portability boundary | Source plus implementation/ABI | Class-file version plus platform dependencies | Target chain and supported EVM fork |
§7 · The same source-level idea, three runtimes
A long-lived counter and temporary working data
The examples express the same source-level intent, but their lifetimes are not equivalent: the C++ and Java counters are bounded by a process or class lifetime, whereas the Solidity counter survives across transactions in protocol state.
Example A — C++
int counter = 0; // static duration; commonly writable data
int resetTo; // static duration; zero-initialized by the language
void add(int amount) { // ABI may use registers and/or stack
int next = counter + amount; // automatic duration; placement may be optimized
counter = next;
}
int* makeBuffer(int n) {
return new int[n]; // dynamic storage; caller must establish ownership
}
Example B — Java
class Counter {
static int counter = 0; // class variable; physical placement is JVM-specific
static int resetTo; // default-initialized to zero
static void add(int amount) {
int next = counter + amount; // logical frame local; JIT placement may differ
counter = next;
}
static int[] makeBuffer(int n) {
return new int[n]; // heap object; reclaimed when unreachable
}
}
Example C — Solidity
contract Counter {
uint256 public counter; // persistent storage across transactions
uint256 public resetTo; // another persistent storage slot, default zero
function add(uint256 amount) external {
uint256 next = counter + amount; // compiler typically uses stack, may spill
counter = next; // persistent state update
}
function makeBuffer(uint256 n) internal pure returns (uint256[] memory) {
return new uint256[](n); // current EVM frame's linear memory
}
}
Placement summary
| Item | C++ | Java | Solidity / EVM |
|---|---|---|---|
counter | Static duration; commonly data section | Class variable; JVM-specific physical placement | Persistent storage |
resetTo | Static duration; guaranteed zero initialization | Class variable; default zero | Persistent storage; default zero |
next | Automatic object; register or stack are common | Logical local-variable slot; JIT may optimize | Usually operand stack; compiler may spill to memory |
| array / buffer | Dynamic storage; ownership required | GC-managed heap object | Frame-local linear memory |
| function input | ABI-defined registers/stack | Frame local-variable array, logically | Calldata loaded to stack or copied to memory as required |
Compiler placement is not source semantics
Comments such as “on the stack” are useful implementation intuition, not universal guarantees. C++ and JVM compilers may use registers or eliminate objects; Solidity's compiler may rearrange or spill stack values while preserving EVM-visible behavior.
§8 · Limits of the analogy
Where the taxonomy leaks
The taxonomy is useful precisely when its limits are visible. Similar purpose does not imply similar lifetime, representation, observability, failure mode, or cost.
Name clashes — the same word denotes different mechanisms
| Term | C++ | JVM | EVM |
|---|---|---|---|
| Stack | Common native call-stack realization; not a required home for every automatic object | Per-thread logical frames, each with locals and an operand stack | Per-frame operand stack only; message-call depth is separate |
| Heap / memory | “Heap” is conventional allocator terminology for dynamic storage | Shared GC-managed object heap | memory is frame-local linear scratch, not an object heap |
| Static / storage | Program-duration or internal-linkage concepts; process-local | Class-associated variables and metadata with JVM-specific layout | storage is mutable cross-transaction protocol state |
Structural mismatches
- EVM memory belongs in the runtime-allocated layer only by analogy: it is a linear arena with no object graph and no per-object freeing.
- Calldata, returndata, and logs are I/O channels rather than memory regions in the same sense as a heap or stack; they are grouped with execution context for comparative convenience.
- Transient storage is transaction-scoped state, not frame-local working memory. It is shared across relevant frames and follows revert semantics.
- C++ “segments” and HotSpot Metaspace are implementation categories; treating them as normative language/JVMS constructs would mix abstraction levels.
§9 · Terminology
Why this is called a data-area taxonomy
“Memory model” is overloaded in standards literature. C++ uses the term for foundational notions such as bytes and memory locations and separately specifies multithreaded executions and data races; Java's Memory Model specifies inter-thread visibility, ordering, and happens-before relations [3]. Neither meaning is the subject of this note.
This document instead concerns storage duration, run-time data areas, execution-frame state, and persistent protocol state. “Runtime data-area taxonomy” is therefore the least ambiguous label.
Preferred usage
Use memory model only when the relevant specification uses it; use runtime data areas, storage organization, or data-area taxonomy for the comparative framework here.
§10 · Quick reference
One precise sentence each
C++23
The language specifies object lifetimes and storage durations; native stacks, registers, sections, and allocator heaps are common but non-normative realizations.
JVM SE 25
The JVMS defines logical shared and per-thread run-time data areas, while HotSpot decides their physical native-memory and compiled-code realization.
EVM
Each frame has operand stack and linear memory, while mutable storage persists across transactions and gas prices execution under fork-specific consensus rules.
| Question | C++23 | JVM / Java | EVM / Solidity |
|---|---|---|---|
| Where is executable code represented? | Program image; commonly code/text sections | Method-area structures; optional compiled-code cache | Account code as EVM bytecode |
| Where is long-lived internal state? | Static/thread-duration objects | Class-associated data, implementation-specific placement | Persistent or transient storage |
| Where are temporary scalar values? | Automatic objects, often registers/stack | Logical frame locals/operand stacks | Operand stack, with possible compiler spills |
| Where are runtime-created aggregates? | Dynamic storage or automatic objects | Java heap | No general object heap; temporary arrays/structs use memory |
| How is working data reclaimed? | Scope destruction, RAII, and explicit/library deallocation | Frame teardown plus garbage collection | Frame memory discard; state updates committed or reverted |
| What persists beyond process exit? | Only externalized data | Only externalized data | Committed contract storage |
| What constrains execution? | Language rules and machine/OS resources | VM configuration, GC/JIT, native resources | Gas and protocol limits |
§11 · Beyond three
Extending the taxonomy cautiously
For another runtime, ask which constructs serve each analytical purpose, then record where the mapping fails.
| Runtime | Code | Long-lived state | Activation / operand | Working data | Context / runtime |
|---|---|---|---|---|---|
| CLR (.NET) | IL + JIT | Metadata + statics | Eval stack / frames | Managed heap (GC) | Thread, AppDomain, GC |
| CPython | Bytecode (.pyc) | Module globals (dict) | Frame objects | Heap (refcount + cycle GC) | Interpreter loop, GIL |
| WebAssembly | Code section | Data / global sections | Value stack | Linear memory | Host runtime, imports |
| Lua VM | Bytecode (proto) | Global table (_G) | Lua stack | Heap (GC) | Lua state, allocator |
The lens is a lens, not a law. Some runtimes blur the boundaries (for example, CPython frame objects are heap allocated); others add dimensions such as explicit I/O channels. Use the taxonomy to formulate questions, then verify every claim against the relevant specification and implementation.
§12 · Final checks
Accuracy checklist for readers
The final edition intentionally marks the claims most likely to be mistaught. Use this list when reviewing slides, exam answers, or implementations derived from the taxonomy.
§13 · Primary sources
References
- ISO/IEC. ISO/IEC 14882:2024, Programming Languages — C++. Publicly accessible working draft: Thomas Köppe, ed., Working Draft, Standard for Programming Language C++, WG21 N4950, 10 May 2023, especially §6.7.5 “Storage duration,” §6.7.1 “Memory model,” and §6.9.2 “Multi-threaded executions and data races.” WG21 N4950.
- Lindholm, T.; Yellin, F.; Bracha, G.; Buckley, A.; Smith, D. The Java Virtual Machine Specification, Java SE 25 Edition. 29 July 2025, §2.5 “Run-Time Data Areas” and §2.6 “Frames.” Oracle JVMS 25.
- Gosling, J.; Joy, B.; Steele, G.; Bracha, G.; Buckley, A.; Smith, D. The Java Language Specification, Java SE 25 Edition. §17.4 “Memory Model.” Oracle JLS 25.
- Masamitsu, J. “JEP 122: Remove the Permanent Generation.” OpenJDK, delivered in JDK 8. Establishes the HotSpot implementation change that removed PermGen and moved class metadata to native memory; the final note treats Metaspace as implementation vocabulary, not a JVMS data-area name. OpenJDK JEP 122.
- Ethereum Foundation. “Ethereum Virtual Machine (EVM).” Describes deterministic state transition, 1024-entry 256-bit stack, memory, persistent storage, transient storage, and gas. ethereum.org EVM documentation. Consensus-sensitive details should be checked against the current Ethereum Execution Layer Specifications.
- Akhunov, A.; Salem, M. “EIP-1153: Transient Storage Opcodes.”
Final Core EIP. Specifies transaction-scoped
TLOAD/TSTORE, frame sharing, revert behavior, and clearing at transaction end. EIP-1153. - Solidity Project. “Transient Storage in Solidity 0.8.24.” Explains Solidity compiler support for EIP-1153 and notes that transient storage is scoped to the current transaction and reset afterward. Solidity blog — transient storage.
- Solidity Project. “Types — Data location,” “Layout in Memory,” and “Layout of State Variables in Storage and Transient Storage.” Data locations; memory layout; storage layout.
- Ethereum Improvement Proposals. “EIP-7569: Hardfork Meta — Dencun.” Lists EIP-1153 among the execution-layer changes activated in the March 2024 Dencun network upgrade. EIP-7569.
Accessed: 26 June 2026. Version-sensitive statements are labeled as specification-level, implementation-specific, or fork-dependent. For legal or production-critical work, consult the cited specifications and the implementation or fork actually in use.