Let’s Order a Java Object: The Hidden Life of “new” by Sahil Khan on June 24, 2025 87 views

Spoiler alert: new
isn’t just a keyword—it’s a full-blown catering service that spins up class loading, memory prep, and object creation behind the scenes.
Pizza myDinner = new Pizza();
To you, that’s dinner.
To the JVM, that’s a mission.
Let’s break it down from your craving for objects to how memory is actually used—sprinkled with insights from the official Java docs. After all, the JVM works hard behind the scenes too.
🛎️ Step 1: Placing the Order — Symbolic Reference Resolution
When you write:
Pizza myDinner = new Pizza();
Java seems to magically conjure a Pizza
out of thin air. But under the hood, the JVM is performing a series of steps that are far from trivial. Before it even allocates memory or initializes the object, it has to resolve what “Pizza” actually means in the JVM’s runtime world.
This process is called symbolic reference resolution. It’s like calling a restaurant and asking for a dish—you say “Pizza”, but the kitchen needs to check whether:
- Pizza is even on the menu
- They have the ingredients
- The recipe (class) is valid
- And they know how to make it (constructor)
Here’s how it unfolds step by step:
🍽️ 1. Class Loading
The JVM checks if the Pizza
class is already loaded. If not, it asks a ClassLoader to find and load the class bytecode from a .class
file or a JAR.
Think of this as checking the recipe binder to see if “Pizza” exists.
🔍 2. Class Verification
Once the bytecode is loaded, the JVM verifies it for correctness and safety. It ensures the bytecode follows the JVM rules—no stack overflows, illegal jumps, or invalid field accesses.
Basically: “Is this a valid recipe, or are you trying to bake cyanide?”
🔧 3. Preparation (Static Memory Allocation)
Now the class’s static fields are allocated and set to their default values (not actual values yet). This sets up the “kitchen counter” for shared ingredients.
Preheating the oven, laying out the dough, grating the cheese.
🔗 4. Symbolic to Direct Reference Resolution
Finally, "Pizza"
(a symbolic name in source code) is resolved into a direct reference to the loaded class object in memory. This is the JVM version of:
“Oh! You meant this Pizza class from com.delivery.food.Pizza? Got it.”
At this point, the JVM is ready to create the object by using the actual constructor method.
🍳 5. Static Initialization (Assigning Actual Values)
After the JVM resolves "Pizza"
to a direct class reference, it runs all the static blocks and initializers and assigns the actual values to static fields. This is where your shared ingredients are precisely measured, seasoned, and laid out on the kitchen counter.
The oven is fully preheated, and the static toppings are spread exactly as the recipe calls for.
This step happens once per class, ensuring that all static data is ready before any individual Pizza is crafted in the heap.
🍳 Step 2: Allocating Kitchen Space — Memory in the Heap
The JVM now knows what “Pizza” is. Time to cook it.
But where?
When you say:
Pizza myDinner = new Pizza();
The JVM doesn’t just throw that object anywhere. It allocates memory for it in a special area called the heap — Java’s shared kitchen, used by all threads.
The heap is the runtime data area from which memory for all class instances and arrays is allocated.
— Java Virtual Machine Specification §2.5.3
But the heap isn’t one big undifferentiated blob. It’s more like a multi-zone kitchen, divided into compartments for better organisation and cleanup.
Here’s how it works:
🍃 Eden Space — Where Objects Are Born
New objects like your Pizza
are first placed in a section of the heap called the Eden Space. It’s part of what’s called the Young Generation — a special zone for short-lived objects.
Think of Eden Space as the prep counter in a restaurant kitchen:
- Fast access
- High turnover
- Quick cleanups
Most objects (like temp strings, or short-lived function variables) die young, so Eden is optimized for rapid allocation and cleanup.
JVM: “Let’s prep this Pizza on the Eden counter — if it’s not needed after serving, we’ll toss it fast.”
🧒 Young Generation vs 🧓 Old Generation
Java heap is split into:
- Young Generation: for new, short-lived objects
- Eden: where objects are first created
- Survivor spaces: if objects live long enough, they “survive” and move here temporarily
- Old (Tenured) Generation: for long-lived objects
- If your
Pizza
hangs around too long (maybe in a static cache?), it moves to the Old Gen.
This division helps the Garbage Collector (GC) work more efficiently. Short-lived junk gets thrown out quickly without scanning the whole heap every time.
🍕 Memory Allocation Process
So when you create a Pizza
:
- JVM calculates how much memory is needed.
- It allocates that memory in the Eden Space.
- JVM attaches some object headers (metadata about class, GC status, etc.)
- Fields are default-initialized (more on that in the next section).
Whenever a new class instance is created, memory space is allocated for it with room for all the instance variables declared in the class type and all the instance variables declared in each superclass of the class type, including all the instance variables that may be hidden.
If there’s not enough room in the Eden Space, the JVM triggers a Minor GC — a quick cleanup sweep of the Young Generation (Eden + Survivor spaces). It tries to clear out dead short-lived objects and promote survivors.
🍕 Think of a Minor GC like clearing the prep counter between lunch and dinner rush — fast, targeted, and usually efficient.
If some objects have survived enough rounds of Minor GCs, they’re deemed long-lived and promoted to the Old Generation. But if Old Gen space also fills up, the JVM initiates a Major GC (a.k.a. Full GC), which scans and cleans the entire heap — including both Young and Old generations.
🧼 Major GC is like a full kitchen deep-clean: more thorough, but slower.
And if even that doesn’t free up enough memory?
throw new OutOfMemoryError();
“Sorry! We ran out of counter space. You’ll have to skip dinner.”
🧾 Object Metadata: What JVM Adds Behind the Scenes
Now, you’ve memory carved out in the heap for your Pizza
. But before it’s handed off for cooking (initialization), the JVM attaches something special to every object: an object header.
This hidden part stores critical JVM metadata—like lock info, identity, and class type.
Think of it as a packing label on your pizza box: it doesn’t change how the pizza tastes, but it tells the JVM what it is, where it came from, and how to handle it.
🧬 Step 3: Dough Meets Cheese — Object Initialization
Once the JVM allocates memory for your object—including space for all fields from the entire inheritance chain—it embarks on a structured, multi-phase initialization process to ensure your object is built correctly and predictably.
1. Default Value Zeroing
Before any initialization code runs, all instance fields—including those declared in your class and those inherited from superclasses—are initialized to their default values:
- Reference types →
null
- Primitives →
0
,false
,0.0
, etc.
This step prepares the memory space, ensuring a clean slate before any further initialization.
2. Superclass Initialization
The JVM then initializes your object’s superclass components in the following order:
- Superclass Initializers and Fields: Executes all instance initializer blocks and field initializations in the textual order they appear in the superclass.
- Superclass Constructor: Executes the constructor body of the superclass.
This sequence ensures that all inherited fields and behavior are fully prepared before subclass-specific initialization begins.
3. Subclass Initialization
After the superclass setup, the JVM proceeds with your subclass initialization:
- Subclass Initializers and Fields: Executes all instance initializer blocks and field initializations in the textual order they appear in the subclass.
- Subclass Constructor: Executes the constructor body of the subclass.
This final step allows you to customize your subclass with the assurance that all inherited components are ready.
Oracle adds:
“Instance variables are initialized in left-to-right order (in textual order as declared in the class). If execution fails, creation ends abruptly.”
— Java Language Specification §12.5
So yes, if crust throws mid-bake… no pizza for you.
🪄 Step 4: Returning the Reference — Stack Meets Heap
Once your Pizza
object is fully baked in the heap, the JVM needs a way to deliver its location back to your code. That’s where the stack comes into play.
🧠 What Goes on the Stack?
- Each thread in Java has its own stack.
- The stack stores method frames (one per method call), containing:
- Primitive local variables
- Object references (not the objects themselves)
When you do:
Pizza myDinner = new Pizza();
myDinner
reference is stored on the stack.- The actual
Pizza
object, with all its toppings, lives in the heap.
🗑️ Step 5: Garbage Collection — Death of a Pizza 🍕
Once you’re done enjoying your Pizza
, Java quietly clears your plate.
But unlike languages like C or C++ where you manually free memory, Java handles this for you using a Garbage Collector (GC) — a background process that reclaims memory from objects that are no longer in use.
🔄 What Triggers GC?
When you write:
Pizza myDinner = new Pizza();
Your Pizza
is born in the heap. But the moment you do:
myDinner = null;
…you’ve effectively told the JVM, “I’m done with this slice.”
Now, if no other references exist to that object, the GC eventually clears it out to make space for new objects.
Garbage Collection is non-deterministic. You can’t predict exactly when an object will be cleaned up—only that it will be once it’s no longer reachable.
🧼 What Makes an Object Eligible for GC?
An object is ready to be collected if:
- No active stack variable, static field, or reachable object graph points to it.
- You’ve dereferenced or reassigned its pointer.
- It’s not strongly reachable anymore.
💡 In other words: if no one’s holding onto your pizza, JVM throws it in the trash.
🪦 Finalization: The Last Goodbye
Historically, Java allowed you to say a final farewell to your objects using the finalize()
method:
@Override protected void finalize() throws Throwable { System.out.println("Goodbye, cruel JVM..."); }
Called by the garbage collector on an object when the GC determines that there are no more references to the object.
finalize()
is deprecated and marked for removal, and should not be relied upon in modern Java.
Instead, Java recommends using:
AutoCloseable
+ try-with-resources (for resources like files, sockets)Cleaner
(for advanced cleanup tasks)PhantomReference
(for low-level memory tracking)
🍕 Bonus: Implicit Object Creation – Surprise Menu Items
Sometimes JVM sneaks in objects without an explicit new
:
- String literals – may create a new String (interned in the String Pool, a special area of the heap that reuses identical values).
🍕 Like the kitchen realising you always order Margherita and handing you one from the shelf instead of baking a new one.
- Boxing, e.g.
Integer x = 242;
- String concatenation, e.g.
"Hi " + name
- Lambdas and method references – hidden helper classes
Like ordering breadsticks you didn’t ask for—and suddenly charging you for them. Thanks, +
.
🍕 Bonus 2: Escape Analysis — When the JVM Gets Smart
Sometimes, the JVM realises an object doesn’t “escape” the method where it’s created — meaning it’s not returned or passed elsewhere. This allows the JIT compiler to skip heap allocation and instead:
- Apply scalar replacement, breaking the object into its individual fields and storing them in locals or registers — no object ever created. (escape analysis)
- Remove unnecessary synchronization if the object doesn’t escape other threads (lock elimination).
🍕 Like making a mini pizza just for the kitchen staff — no need to send it to the front counter; you prep and eat it right there in the back.
This optimisation leads to faster execution and less GC pressure, all without changing a line of your code.
The JVM is your prep-cook that watches your habits and preps pizza just the way you like — ahead of time, optimized to reduce waiting, and with less wastage.
🪦 Full Lifecycle Recap
📝 Author’s Note
Thanks for tagging along on the journey of Pizza myDinner = new Pizza();
.
What looks like a simple line kicks off a full JVM production—from class loading to memory magic and cleanup. If this post gave you a new perspective on new
, I’d love to hear it.
Bon appétit, and happy coding!
— Sahil Khan