What is stored in Heap
The JVM Heap is the primary runtime data area created when the Java Virtual Machine starts up. It is globally shared across all threads (Shared Memory): any object instantiated...
🟢 Junior Level
The JVM Heap is the primary runtime data area created when the Java Virtual Machine starts up. It is globally shared across all threads (Shared Memory): any object instantiated in the heap is accessible by any thread that holds a reference to it.
What Resides in the Heap
- All Class Instances: Every object created using the
newkeyword (e.g.,new User(),new HashMap<>()). - All Arrays: Both object arrays (
String[]) and primitive arrays (int[],byte[]). In Java, arrays are true heap objects. - The String Constant Pool: String literals and strings explicitly interned via
String.intern(). - Static Class Variables: Since Java 8, static variables are stored in the Heap as fields of the mirror
java.lang.Classobject. - Boxed Primitive Wrappers: Instances of
Integer,Double,Boolean, whether created explicitly or through autoboxing (Integer.valueOf(42)).
The Classic Junior Trap: “Where Do Primitives Live?”
The storage location of a primitive variable depends strictly on where it is declared:
- Local Variable in a Method: Stored in the Stack Frame of the executing thread (
int count = 10;). - Instance Field of a Class: Stored on the Heap within the memory footprint of the enclosing object (
class Account { int balance = 100; }). - Element of a Primitive Array: Stored on the Heap inside the contiguous memory block of the array object (
int[] values = new int[5];).
public class MemoryAllocationDemo {
private int id = 101; // Heap (instance field of MemoryAllocationDemo)
public void process() {
int localCount = 42; // Stack (local variable of method)
Point point = new Point(5, 10); // Reference 'point' in Stack -> Point object in Heap
int[] data = new int[]{1, 2}; // Reference 'data' in Stack -> int[] array in Heap
}
}
🟡 Middle Level
Java Object Layout (JOL) in 64-Bit HotSpot
Every object allocated on the 64-bit JVM Heap adheres to a structured, 8-byte aligned layout:
┌────────────────────────────────────────────────────────┐
│ OBJECT HEADER │
├────────────────────────────────────────────────────────┤
│ 1. Mark Word (8 bytes) │
│ - Identity HashCode, Age bits (0–15), Lock state │
├────────────────────────────────────────────────────────┤
│ 2. Klass Word (4 bytes with Compressed Class Pointers) │
│ - Pointer to class metadata in Metaspace │
├────────────────────────────────────────────────────────┤
│ 3. Array Length (4 bytes, present ONLY in arrays) │
├────────────────────────────────────────────────────────┤
│ 4. Instance Data (Field values) │
│ - Primitive fields and reference pointers │
├────────────────────────────────────────────────────────┤
│ 5. Padding (Alignment zeroes) │
│ - Pads total object footprint to a multiple of 8B │
└────────────────────────────────────────────────────────┘
Field Packing & Reordering
To prevent memory gaps, the JVM does not place fields in memory in the order they are declared in Java source code. Instead, it reorders them by size:
double/long(8 bytes)int/float(4 bytes)short/char(2 bytes)byte/boolean(1 byte)- Object references (OOPs — 4 or 8 bytes)
Memory Footprint Calculations (Compressed OOPs):
new Object(): 8B (Mark Word) + 4B (Klass Word) + 4B (Padding) = 16 bytes.new Integer(42): 8B (Mark Word) + 4B (Klass Word) + 4B (intvalue) = 16 bytes.new Long(42L): 8B (Mark Word) + 4B (Klass Word) + 8B (longvalue) + 4B (Padding) = 24 bytes.
String Pool Evolution & Compact Strings (Java 9+)
- Relocation of the String Pool:
- Java 6 and earlier: The String Pool resided in PermGen (Permanent Generation), which had a fixed size and frequently failed with
OutOfMemoryError: PermGen space. - Java 7+: The String Pool was moved into the Heap, allowing unreferenced interned strings to be reclaimed by standard Garbage Collection.
- Java 6 and earlier: The String Pool resided in PermGen (Permanent Generation), which had a fixed size and frequently failed with
- Compact Strings (Java 9 / JEP 254):
- Historically,
java.lang.Stringstored characters as achar[], where every character occupied 2 bytes (UTF-16), even for pure ASCII text. - Since Java 9, strings use
byte[] valuepaired with a 1-byte encoding flag (byte coder). If all characters fit into Latin-1 (ISO-8859-1), each character occupies only 1 byte, reducing overall enterprise heap consumption by 15–20%.
- Historically,
Compressed OOPs & The 32 GB Limit
On 64-bit JVMs, HotSpot enables Compressed Ordinary Object Pointers (-XX:+UseCompressedOops) by default for heaps under 32 GB:
- Because objects are aligned to 8-byte boundaries, the lowest 3 bits of any object address are always zero (
000). - The JVM shifts memory addresses right by 3 bits (
address >> 3) to store 64-bit addresses inside 32-bit (4-byte) reference fields. - When loading a reference into a CPU register, it shifts left (
address << 3). - This allows 4-byte references to address up to: \(2^{32} \times 8 \text{ bytes} = 32 \text{ GB}\)
[!WARNING] Setting
-Xmx33gdisables Compressed OOPs, doubling reference sizes to 8 bytes and consuming 3–4 GB of additional RAM purely for pointers.
🔴 Senior Level
Shallow Size vs Retained Size
When diagnosing memory leaks and analyzing Heap Dumps using tools like Eclipse MAT or JProfiler, understanding object size metrics is critical:
┌────────────────────────────────────────────────────────┐
│ Shallow Size = The memory consumed by the object │
│ itself (Header + immediate fields + padding) │
├────────────────────────────────────────────────────────┤
│ Retained Size = Shallow Size │
│ + The size of ALL objects reachable EXCLUSIVELY │
│ through this object (Dominator Tree) │
└────────────────────────────────────────────────────────┘
Concrete Example: new ArrayList<>(100)
- Shallow Size: 12 bytes (Header) + 4 bytes (reference to array) + 4 bytes (
int size) + 4 bytes (padding) = 24 bytes. - Retained Size: Its own 24 bytes + the backing
Object[100]array (424 bytes) = 448 bytes. If 100Userobjects are placed into this list and referenced nowhere else in the application, their combined heap footprint is added to the list’s Retained Size.
Humongous Objects in the G1 Garbage Collector
The Garbage-First (G1) collector partitions the heap into equal-sized regions (from 1 MB to 32 MB):
- An object whose size exceeds 50% of a G1 region is classified as a Humongous Object.
- Specialized Allocation Rules:
- Humongous objects bypass the Young Generation (Eden/Survivor) entirely and are allocated directly into a contiguous block of regions in the Old Generation.
- They are not moved during standard Young GC evacuation pauses (avoiding expensive memory-copy overhead for large arrays).
- Fragmentation Hazard: If the JVM cannot find a contiguous block of unfragmented G1 regions to accommodate a large array, it triggers an emergency Full GC Stop-the-World pause.
Inspecting Object Footprint via JOL (Java Object Layout)
The OpenJDK JOL tool allows inspecting memory layout directly:
import org.openjdk.jol.info.ClassLayout;
public class JolInspection {
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(Order.class).toPrintable());
}
}
class Order {
private long id; // 8 bytes
private int status; // 4 bytes
private boolean paid; // 1 byte
private Object payload; // 4 bytes (compressed OOP)
}
JOL Output on a 64-bit HotSpot JVM:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 8 (object header: mark word) ...
8 4 (object header: class word) ...
12 4 int Order.status 0
16 8 long Order.id 0
24 4 Object Order.payload null
28 1 boolean Order.paid false
29 3 (alignment/padding gap)
Instance size: 32 bytes
Note: The JVM reorders status before id to fill the 4-byte gap immediately following the 12-byte header, achieving 8-byte alignment without padding waste.
Off-Heap Memory: Bypassing the Heap
When managing massive datasets (in-memory caches, messaging queues like Kafka, or database buffers), storing tens of gigabytes in the Heap causes long GC pause times. Applications utilize Off-Heap Memory:
java.nio.ByteBuffer.allocateDirect(): Allocates native OS memory viamalloc().- Project Panama (Foreign Function & Memory API, Java 22+):
ArenaandMemorySegmentprovide deterministic, zero-copy native memory allocation without Garbage Collector overhead.
4 Tricky Questions
1. Where are primitive variables stored: in the Heap or in the Stack?
Answer: The location of primitive variables depends strictly on their declaration scope:
- Local variables: Primitives declared inside a method (
int x = 10;) are stored in the Stack Frame of the calling thread. - Instance fields: Primitive fields declared inside a class (
class User { int age = 20; }) are stored on the Heap within the memory layout of the enclosing object instance. - Static fields: Static primitive fields are stored on the Heap within the
java.lang.Classmirror object. - Arrays: Elements of primitive arrays (
int[] arr = new int[10];) are always stored on the Heap.
2. Where are static class variables stored in modern Java (Java 8+)?
Answer: In Java 8 and later, static variables are stored directly in the Java Heap. They reside as instance fields within the java.lang.Class object associated with the loaded class. Prior to Java 8, static variables were stored in PermGen (Permanent Generation), which frequently caused OutOfMemoryError: PermGen space. In contrast, the modern Metaspace area is stored in native OS memory and holds only class metadata (method bytecodes, constant pool, annotations), not static variable values.
3. What is the difference between an object’s Shallow Size and its Retained Size in memory profiling?
Answer: Shallow Size is the physical memory consumed exclusively by the object itself—including its header, primitive field values, references to other objects (4 or 8 bytes each), and alignment padding. Retained Size is the total heap memory that would be freed if that specific object were garbage collected. It equals the object’s shallow size plus the shallow sizes of all other objects that are exclusively reachable from this object (as computed by the heap Dominator Tree).
4. What are Humongous Objects in the G1 Garbage Collector, and why can they cause premature Full GC pauses?
Answer: In G1 GC, a Humongous Object is any object whose size exceeds 50% of the standard G1 Region size (which ranges from 1 MB to 32 MB). Humongous objects are allocated directly into the Old Generation within a sequence of contiguous regions, bypassing Eden and Survivor spaces. Because they require physically adjacent regions, they can trigger premature Full GC Stop-the-World pauses even when plenty of total free heap exists, if free regions are fragmented across the heap and no single contiguous span of regions is large enough to fit the object.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“The Java Heap is the shared runtime memory area where all object instances, arrays, the String Constant Pool, and static class fields reside. Every object in the heap has an Object Header consisting of a Mark Word (storing hash code, lock status, and 4-bit GC age) and a Klass Word (pointing to class metadata). To minimize memory waste, HotSpot uses Compressed OOPs below 32 GB, reducing reference pointers to 4 bytes. When analyzing memory leaks, we evaluate Shallow Size (the object’s own footprint) versus Retained Size (the total memory freed if the object and its exclusive dependency tree are collected).”
Key Architectural Takeaways
- Heap Contents: All instances created via
new, all arrays, String Pool, static variables. - Primitives Rule: Local primitives $\to$ Stack; class fields and array elements $\to$ Heap.
- Object Header: Mark Word (8B) + Klass Word (4B/8B) + Array Length (4B for arrays).
- Minimum Object Size:
new Object()occupies exactly 16 bytes. - String Pool: Resides in the Heap (since Java 7); uses Compact Strings with Latin-1 encoding (since Java 9).
- G1 Humongous Objects: Allocated directly into Old Gen if size $> 50\%$ of region size; causes heap fragmentation.
Red Flags & Anti-Patterns
- 🚩 Claiming “Primitives are always stored on the Stack”: Forgetting that instance fields and array elements reside in the Heap.
- 🚩 Asserting that the String Pool is in Metaspace: Moved to the Heap in Java 7.
- 🚩 Assuming an empty
new Object()takes 0 bytes: Overlooking the 12-byte header and 4-byte padding (16 bytes total). - 🚩 Ignoring the 32 GB boundary: Setting heap size to 33–34 GB, which disables Compressed OOPs and degrades CPU cache utilization.
Related Topics
- What is the difference between Heap and Stack — Fundamental memory comparison
- What is stored in Stack — Stack frame layout and execution mechanics
- What are generations in GC — Eden, Survivor, and Old Gen layout
- What is Metaspace (or PermGen) — Off-heap metadata storage