What is stored in Heap?
4. String Pool saves memory on strings 5. Static collections — main source of leaks 6. NUMA — enable on multi-processor servers. Do not enable on single-processor — overhead wit...
Junior Level
Heap — a virtual memory area (from tens of MB to terabytes) allocated by the JVM for storing objects. Separated from Stack because objects outlive method calls.
What exactly:
- Everything created via
new→ in Heap - Arrays → in Heap
- Strings → in Heap (String Pool)
- Static fields → in Heap
Example:
public class Example {
static int count = 0; // Static field → in Heap
public void method() {
int x = 10; // Primitive → in Stack
String s = "hello"; // Reference in Stack, "hello" → in Heap
User user = new User(); // Reference in Stack, object → in Heap
int[] arr = new int[5]; // Reference in Stack, array → in Heap
}
}
Heap Size:
- Configurable:
-Xms(initial),-Xmx(maximum) - Default: depends on system (usually 1/4 of RAM)
- Overflow error:
OutOfMemoryError: Java heap space
Middle Level
What is stored in Heap
1. Object instances:
new User("Ivan") // User object in Heap
new ArrayList<>() // ArrayList object + internal arrays
2. Arrays (even of primitives):
int[] numbers = new int[1000]; // Array is an object → in Heap
byte[] data = new byte[1024]; // Even byte[] is an object in Heap
3. Static fields (since Java 8):
public class Config {
private static String DB_URL = "jdbc:..."; // In Heap (Class object)
}
4. String Pool:
String s1 = "hello"; // In String Pool (part of Heap)
String s2 = "hello"; // Same reference (pool saves memory)
Object Anatomy in Memory
Object in Heap:
┌─────────────────────────┐
│ Header (12-16 bytes) │
│ Mark Word (8 bytes) │
│ Klass Pointer (4 bytes)│
├─────────────────────────┤
│ Instance Data (fields) │
│ int age = 25 (4 bytes)│
│ String name (4 bytes - reference)│
├─────────────────────────┤
│ Padding (to 8 bytes) │
└─────────────────────────┘
Mark Word stores:
- Object hash code
- Age for GC (how many collections survived)
- Lock state
Klass Pointer:
- Pointer to class metadata in Metaspace
- 4 bytes with Compressed OOPs (< 32 GB Heap)
- 8 bytes without Compressed OOPs (> 32 GB Heap)
Compressed OOPs Optimization
32-bit JVM: pointers = 4 bytes
64-bit JVM: pointers = 8 bytes
Compressed OOPs (Heap < 32 GB):
Pointer = 4 bytes (shift × 8)
→ 50% memory savings on references!
→ More data in CPU cache
Threshold: 32 GB Heap
2^32 × 8 bytes = 32 GB
Why 31 GB is faster than 33 GB:
- 31 GB → Compressed OOPs enabled → -50% memory on references
- 33 GB → Compressed OOPs disabled → more RAM, but slower
- Often 31 GB with Compressed OOPs is faster than 33 GB without
Common mistakes
- Storing too much in Heap
// ❌ Unbounded cache static Map<String, Object> cache = new HashMap<>(); cache.put(key, value); // Grows infinitely → OOM! - Creating unnecessary objects
// ❌ Garbage in Heap String s = new String("hello"); // Two objects: // 1. Literal "hello" in String Pool (object #1) // 2. new String() — wrapper over literal (object #2) // ✅ String s = "hello"; // One object from pool - Leaks through static collections
// ❌ Static collection grows infinitely static List<String> history = new ArrayList<>(); history.add(data); // Never cleaned!
Senior Level
Heap Structure (HotSpot JVM)
Heap Layout:
┌──────────────────────────────────────────┐
│ Young Generation │
│ Eden (80%) │
│ Survivor 0 (10%) │
│ Survivor 1 (10%) │
├──────────────────────────────────────────┤
│ Old Generation (Tenured) │
│ Long-lived objects │
├──────────────────────────────────────────┤
│ Humongous Regions (G1) │
│ Objects > 50% of region │
└──────────────────────────────────────────┘
TLAB (Thread Local Allocation Buffer)
Problem: hundreds of threads allocating simultaneously → contention
Solution: each thread gets its own piece of Eden
Thread 1: TLAB[64KB] → pointer bumping
Thread 2: TLAB[64KB] → pointer bumping
Thread 3: TLAB[64KB] → pointer bumping
Allocation in TLAB:
obj_ptr = thread.tlab_ptr
thread.tlab_ptr += obj_size
→ 0 synchronization!
→ Faster than malloc() in C++!
PLAB (Promotion Local Allocation Buffer):
→ Analog of TLAB for Old Gen
→ Used when copying surviving objects
NUMA-Aware Heap
NUMA (Non-Uniform Memory Access):
CPU 1 ── Local Memory (fast, 100ns)
CPU 2 ── Remote Memory (slow, 150ns)
-XX:+UseNUMA:
→ JVM allocates memory locally for each CPU
→ +10-20% performance on multi-processor servers
Important for Highload with > 2 CPU sockets
Object Layout Deep Dive
64-bit JVM, Compressed OOPs:
┌─────────────────────────────────┐
│ Mark Word (8 bytes) │
│ Hash: 31 bits │
│ Age: 4 bits (max 15) │
│ Lock: 2 bits │
│ GC: 1 bit │
├─────────────────────────────────┤
│ Klass Pointer (4 bytes) │
│ → Shift × 8 → address in Metaspace│
├─────────────────────────────────┤
│ Array Length (4 bytes) │ ← only for arrays
├─────────────────────────────────┤
│ Instance Data │
│ long (8 bytes) │
│ int (4 bytes) │
│ short (2 bytes) │
│ byte (1 byte) │
│ ref (4 bytes with Compressed)│
├─────────────────────────────────┤
│ Padding (to multiple of 8) │
└─────────────────────────────────┘
→ Minimum object: 16 bytes (empty Object)
→ Integer: 24 bytes (16 header + 4 int + 4 padding)
Memory Alignment
CPU reads memory in words (8 bytes = 64 bits)
Unaligned access:
[1234][5678][9...]
^ data spans two words → 2 read cycles
Aligned access:
[1234][5678][9...]
^ data in one word → 1 read cycle
→ JVM automatically pads to 8 bytes
→ Group fields by size: long/long → int/int → short → byte
Heap Monitoring
// Runtime API
Runtime rt = Runtime.getRuntime();
long max = rt.maxMemory(); // -Xmx
long total = rt.totalMemory(); // Current committed
long free = rt.freeMemory(); // Free
long used = total - free; // Used
// MemoryMXBean
MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = mem.getHeapMemoryUsage();
Future: Project Valhalla
// Value Types (future Java):
public final class Point {
public final int x;
public final int y;
}
// Now:
Point[] arr = new Point[100];
→ 100 objects in Heap (1600 bytes of headers)
→ 100 references (400 bytes)
→ Total: ~4 KB overhead
// With Value Types:
Point[] arr = new Point[100];
→ Data stored directly in the array!
→ No headers, no references
→ Cache locality × 10
Production Experience
Real scenario: 33 GB Heap slower than 31 GB
- Application: Spring Boot,
-Xmx33g - Compressed OOPs disabled → 8 byte pointers
- L3 cache miss rate: 25%
- Solution:
-Xmx30g→ Compressed OOPs enabled - Result: L3 miss rate 12%, +15% throughput
Best Practices
- Avoid > 32 GB without need (Compressed OOPs)
- Group fields by size for alignment
- TLAB — object allocation is almost free
- String Pool saves memory on strings
- Static collections — main source of leaks
- NUMA — enable on multi-processor servers. Do not enable on single-processor — overhead without benefit.
- Object Layout affects cache locality
Senior Summary
- Heap stores: objects, arrays, static fields, String Pool
- Object Layout = Header (Mark Word + Klass) + Data + Padding
- Compressed OOPs = 32 GB threshold → 50% savings on references
- TLAB = lock-free allocation → faster than malloc()
- NUMA = local memory for each CPU → +10-20%
- Memory Alignment = multiple of 8 bytes → 1 read cycle
- Value Types (Valhalla) = store by value → no overhead
- 31 GB is often faster than 33 GB due to Compressed OOPs
Interview Cheat Sheet
Must know:
- In Heap: all objects (
new), arrays (even primitives), static fields, String Pool - Minimum object on 64-bit JVM with Compressed OOPs: 16 bytes (header 12 + padding)
- Compressed OOPs compress references from 8 to 4 bytes when Heap < 32 GB
- TLAB — thread-private buffer in Eden, allocation = pointer bumping (0 synchronization)
- String Pool — part of Heap, string literals are reused
- Static collections — main source of memory leaks
- 31 GB Heap is often faster than 33 GB due to Compressed OOPs
Common follow-up questions:
- Why does
new String("hello")create 2 objects? — Literal “hello” in String Pool +new String()wrapper - What does Mark Word store? — Hash code, age for GC, lock state
- What is PLAB? — Promotion Local Allocation Buffer, analog of TLAB for Old Gen when copying surviving objects
- Why are static collections a common leak? — Static field = GC Root, objects are never collected
Red flags (DO NOT say):
- “Primitives are stored in Heap” — local primitives are stored in Stack
- “String Pool is in Metaspace” — since Java 7, String Pool is in Heap
- “Heap is cleaned automatically on method return” — Heap is cleaned by GC, Stack is cleaned on method return
Related topics:
- [[1. What is the difference between Heap and Stack]]
- [[3. What is stored in Stack]]
- [[6. What is a memory leak in Java]]
- [[8. What are generations in GC]]
- [[11. What is Metaspace (or PermGen)]]