How the JVM Allocates Objects and Arranges Their Memory Layout
This article walks through the JVM's six-step process for creating a new object, explains heap allocation strategies and thread‑local buffers, details escape analysis optimizations, reveals the exact memory layout of an object including header and padding, compares handle versus direct pointer access, and describes reachability‑based garbage collection with the four Java reference types.
Object Creation and Memory Layout in the JVM (JDK 8/17)
The discussion centers on the exact steps the JVM performs when executing new Order(), how heap memory is allocated, the role of escape analysis, the concrete object layout, reference handling, reachability‑based garbage collection, and the four Java reference types.
1. What the JVM Does When new Is Executed
Class‑load check : Verify that the class Order is loaded, linked and initialized; otherwise trigger class loading.
Allocate memory : Compute the object size and carve out a region from the heap.
Initialize to zero : Fill the allocated space (excluding the object header) with zeros, which explains default primitive values.
Set object header : Populate the header with class metadata, hash code, GC age, lock state, etc.
Execute <init> : Invoke the constructor to run field initializations and user code.
Store reference : Place the resulting reference into the local variable slot.
new Order // steps 1‑4: allocate, zero, set header
dup // duplicate reference for constructor
invokespecial Order.<init> // step 5: run constructor
astore_1 // step 6: store into local variableKey ordering: zero‑initialize before constructor execution.
2. How Heap Memory Is Allocated
Pointer bump (Bump the Pointer) : When the heap is contiguous, a pointer is simply moved forward by the object size.
Free list : When the heap is fragmented, the JVM maintains a list of free blocks and selects a suitably sized one.
The choice depends on the garbage collector. Collectors that compact (Serial, ParNew, G1) use pointer bump; non‑compacting collectors (CMS) rely on the free list.
Concurrency strategies:
CAS + retry : Update the allocation pointer atomically; retries add overhead under high contention.
TLAB (Thread‑Local Allocation Buffer) : Each thread gets a private buffer in Eden, allowing lock‑free pointer bump. When a TLAB is exhausted the thread falls back to CAS to obtain a new buffer. TLABs are enabled by default ( -XX:+UseTLAB).
3. Escape Analysis – When Objects Stay Out of the Heap
Escape analysis determines whether an object’s scope is confined to a method or thread. If the JIT concludes that an object does not escape, three optimizations become possible:
Stack allocation : Allocate the object on the stack and discard it when the method returns.
Scalar replacement : Eliminate the object entirely, breaking it into individual scalar fields stored as locals.
Lock elision : Remove synchronization for objects that cannot be accessed by other threads.
public int calcTotal() {
Order order = new Order(); // order never escapes
order.setAmount(100);
return order.getAmount(); // returns int, object itself never escapes
}With escape analysis enabled by default ( -XX:+DoEscapeAnalysis), the JIT may replace order with a plain int amount variable, avoiding heap allocation. Disabling it with -XX:-DoEscapeAnalysis dramatically increases GC activity in tight loops that create many short‑lived objects.
4. Object Memory Layout
In HotSpot an object consists of three parts:
Header (Mark Word 8 B + Klass pointer 4 B on a 64‑bit VM with compressed oops).
Instance data : Fields declared in the class and its super‑classes, reordered for size efficiency.
Padding : Zero‑filled bytes to make the total size a multiple of 8 B.
Example class:
class Order {
long userId; // 8 B
int amount; // 4 B
}JOL output on a 64‑bit VM with compressed oops:
Order object internals:
OFFSET SIZE TYPE DESCRIPTION
0 8 (object header: mark word)
8 4 (object header: klass pointer)
12 4 int Order.amount
16 8 long Order.userId
Instance size: 24 bytesThe total size (24 B) is a multiple of 8. A plain new Object() occupies 16 B (12 B header + 4 B padding).
Compressed oops compress 64‑bit references to 32 bits, allowing a 32 GB heap ( 2³² × 8 = 32GB). When the heap exceeds 32 GB compression is disabled, each reference occupies 8 B and effective heap capacity may decrease.
5. Object Access: Handle vs Direct Pointer
Handle access : A handle pool stores a pointer to the object; the reference points to the handle. Moving the object only requires updating the handle, at the cost of an extra indirection.
Direct pointer access : The reference stores the object's address directly; the object header contains the klass pointer. This is faster for the common case of frequent object access.
HotSpot adopts the direct‑pointer approach, accepting the extra work during GC moves in exchange for faster normal accesses.
6. Determining Object Death – Reachability Analysis
Instead of reference counting (which cannot reclaim cyclic structures), the JVM uses reachability analysis. Starting from a set of GC Roots (stack locals, static fields, constant‑pool entries, JNI references, synchronized lock holders, etc.), the VM traverses reference chains. Objects not reachable from any root are considered dead and eligible for collection.
class Node { Node next; }
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
a = null;
b = null; // both objects become unreachable and are reclaimedUnreachable objects undergo two marking phases; if they override finalize(), they may be queued for finalization before actual reclamation (finalization deprecated since JDK 9).
7. Four Types of References
StrongReference (Strong): Never reclaimed, even on OOM. Typical use: Object o = new Object(); SoftReference (Soft): Reclaimed only when memory is low. Typical use: memory‑sensitive caches.
WeakReference (Weak): Reclaimed on any GC. Typical use: ThreadLocal keys, WeakHashMap.
PhantomReference (Phantom): get() always returns null; reclaimed at any time. Typical use: cleanup of off‑heap resources via ReferenceQueue.
These reference levels let developers finely control object lifetimes: soft references for caches that survive until memory pressure, weak references for auxiliary structures that disappear as soon as a GC occurs, and phantom references for post‑finalization cleanup.
Summary
New object creation follows six ordered steps: class‑load check → allocate memory → zero‑initialize → set header → execute <init> → store reference.
Heap allocation uses pointer bump for compacting collectors and free‑list for non‑compacting collectors; TLAB provides lock‑free per‑thread allocation with CAS fallback.
Escape analysis (enabled by default) allows stack allocation, scalar replacement, and lock elision for non‑escaping objects.
Object layout = Mark Word (8 B) + Klass pointer (4 B with compressed oops) + instance fields + padding to 8‑byte alignment; new Object() = 16 B, Order = 24 B.
Compressed oops give a 32 GB addressable heap; they are disabled when the heap exceeds 32 GB.
HotSpot uses direct‑pointer references for faster access, accepting extra GC update cost.
Reachability analysis from GC Roots determines object liveness, avoiding the cyclic‑reference problem of reference counting.
Four reference types (strong, soft, weak, phantom) provide graded control over reclamation timing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
