Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
104: Memory Areas: Heap, Stack, and Metaspace
Where does my data actually go when I create an object?
This is the classic "Stack vs. Heap" question. The simplest way to think about it is: the Stack is for execution and local references, while the Heap is for the actual data.
Imagine you're writing a method to process a customer's order. Look at this snippet:
public void processOrder() {
int orderId = 101;
Customer customer = new Customer("Jane Doe");
}
Here is what's happening under the hood: The orderId is a primitive int, so it lives directly on the Stack. It's fast, it's local, and it disappears the moment the method finishes. But the Customer object? That's a different story. The variable customer is just a reference (essentially a memory address) that lives on the Stack, but the actual Customer object—the one holding the string "Jane Doe"—is allocated on the Heap.
I like to describe the Heap as a big, messy warehouse. Any time you use the new keyword, you're asking Java to find a spot in that warehouse to store your object. The Stack is more like your immediate workbench; it's organized, tiny, and only holds what you're working on right now.
Wait, what exactly is the Metaspace?
You might see older tutorials talking about "PermGen" (Permanent Generation). Forget about that; it was replaced in Java 8. Now we have Metaspace.
If the Heap is where your objects live, the Metaspace is where the blueprint for those objects lives. It stores class metadata, method definitions, and the constant pool. For instance, if you have 10,000 Customer objects on the Heap, you still only have one Customer.class definition in the Metaspace. It tells the JVM, "Okay, every Customer object should have a name string and an ID integer."
The big difference here is that Metaspace isn't part of the Heap; it uses native memory (RAM provided by the OS). In the old PermGen days, we'd often hit a hard limit and crash with an OutOfMemoryError: PermGen space. Metaspace is much more flexible because it can grow automatically, though I've still seen it crash in environments where someone is dynamically generating thousands of classes at runtime using bytecode libraries.
Why do some crashes say 'StackOverflow' and others say 'OutOfMemory'?
It all comes down to which "bucket" you've filled up. The Stack is very small and has a fixed size per thread. The Heap is huge and shared across the whole application.
You'll hit a StackOverflowError when you push too many "frames" onto the stack. This almost always happens during infinite recursion. I've seen juniors do this when trying to traverse a file directory or a tree structure without a proper exit condition:
public void recursiveCrash() {
recursiveCrash(); // No exit condition!
}
In this case, Java keeps adding a new frame for recursiveCrash() on top of the previous one until the Stack simply runs out of room. It doesn't matter if you have 64GB of RAM; the Stack size is usually just a few megabytes.
An OutOfMemoryError (OOM), however, happens when the Heap is full and the Garbage Collector (GC) can't find anything to delete to make room for new objects. This usually happens if you're hoarding data in a static list or creating objects in a loop faster than the GC can clean them up:
List<byte[]> leak = new ArrayList<>();
while (true) {
leak.add(new byte[1024 * 1024]); // Adding 1MB chunks forever
}
One is a "too many calls" problem (Stack); the other is a "too much stuff" problem (Heap).
📋 Practical Task
Exercise: Breaking the JVM: Stack vs. Heap
To truly understand these memory areas, you need to see them fail. Your task is to create a single Java class with two separate methods that demonstrate the two different memory crashes discussed in the lesson.
- Method 1 (The Stack Buster): Create a method that calls itself recursively without a termination condition. This should trigger a
java.lang.StackOverflowError. - Method 2 (The Heap Filler): Create a method that initializes a
Listand enters awhile(true)loop, adding large objects (like largeint[]arrays orbyte[]buffers) to that list until the JVM runs out of heap space. This should trigger ajava.lang.OutOfMemoryError: Java heap space.
Requirements:
1. Write the code in a class named MemoryCrashDemo.
2. In your main method, call only one of these methods at a time (comment out the other), as the first crash will stop the JVM entirely.
3. Observe the console output and identify exactly which error is thrown for each method.
There are no comments for now.