Objects All the Way Down
A Deep Journey into Java's Class and Object Model
From Syntax to Silicon: How Java Really Thinks
[Link] :Player @ 0x500
String name name = "Ravi"
int health new health = 100
+ static int count
+ Player(String n)
+ void takeDamage(int)
+ int getHealth()
:Player @ 0x800
name =Instance
"Amit"
new
Blueprint
health = 80
Covering Object-Oriented Design, Encapsulation, Static Members,
and the JVM Memory Model from First Principles
Edition 1.0 A Guide for the Curious Engineer
Objects All the Way Down
A Deep Journey into Java’s Class and Object Model
Edition 1.0
This book is intended for educational purposes. All code examples are provided for
learning and may be freely adapted.
Java is a registered trademark of Oracle Corporation.
Composed in LATEX using the book document class, TikZ for diagrams, and listings
for code.
To every programmer who ever asked “but why?”
The question is always more important than the answer.
Preface
There is a particular kind of satisfaction that comes from understanding
something not just how it works, but why it is designed the way it is, and what
is physically happening inside the machine when you press the run button. This
book was born from exactly that kind of curiosity.
The conversation that seeded this text began simply: a student writing
a Player class for a game. It ended many hours later with that same stu-
dent reasoning about the 8-byte alignment requirements imposed by the JVM’s
Compressed OOPs optimization, explaining why an object of 20 bytes must
be padded to 24, and reconstructing the internal layout of an object’s header
from first principles. That arc—from a single class to the silicon-level physics
of Java—is the arc this book follows.
Programming books typically fall into two camps: the tutorial, which teaches
you what to type; and the internals manual, which explains how the machine
works. This book attempts a third path: it teaches through the natural progres-
sion of a genuine learning conversation, where every answer births a new and
deeper question. Curiosity is not a bug here—it is the engine.
You will find this book useful if you are a student who wants to understand
object-oriented programming not just as a set of rules to memorize but as a co-
herent, beautiful design philosophy. It is also valuable for working developers
who have written Java for years but have never looked underneath the surface
to understand what the JVM is doing on their behalf.
Each chapter opens with a motivating question, proceeds through layered
explanations (intuition first, formalism second), and closes with exercises rang-
ing from conceptual reflection to implementation challenges. The later chap-
ters venture into JVM internals—memory layout, object headers, field align-
ment, and the brilliant engineering trick known as Compressed OOPs—in lan-
guage that is rigorous but never dry.
You do not need to be an expert to read this book. You need only a working
knowledge of basic Java syntax and, more importantly, an appetite for under-
standing things deeply.
The Author
iv
How to Use This Book
Boxes and Call-outs
Throughout the text you will encounter coloured boxes.
• Green boxes (Definition) introduce precise technical defini-
tions.
• Blue boxes (Deep Insight) expand on an idea beyond the stan-
dard explanation.
• Orange boxes (Common Misconception) warn against fre-
quent errors in reasoning.
• Grey boxes (Example) present worked examples.
• Purple boxes (Exercises) provide practice problems.
• Teal boxes (Summary) close each chapter with key takeaways.
Code Listings
All Java code is typeset in a monospaced font with syntax highlight-
ing. Lines are numbered for easy reference. You are strongly en-
couraged to type the examples rather than copy them—the muscle
memory matters.
Exercises Each chapter’s exercises are graduated in difficulty. The first few
questions are conceptual and test comprehension. Later questions
require writing or debugging code. The final question in each set
is marked (Open-Ended) and has no single correct answer—it is
designed to make you think beyond the chapter.
Reading Path
Read Parts I through III sequentially if you are new to object-oriented
programming. Part IV (JVM Internals) can be read independently if
you already understand OOP and wish to explore memory architec-
ture.
v
Contents
Preface iv
How to Use This Book v
I The Architecture of Thought
Classes, Objects, and the Nature of Identity 1
1 Blueprint and Instance: The Philosophy of Class and Object 2
1.1 Why This Distinction Matters . . . . . . . . . . . . . . . . . . . 2
1.2 The Class: A Blueprint . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 The Object: A Living Instance . . . . . . . . . . . . . . . . . . . 3
1.4 Fields: The Memory of an Object . . . . . . . . . . . . . . . . . 4
1.5 Methods: The Capabilities of an Object . . . . . . . . . . . . . . 5
1.6 Putting It Together: A Complete First Example . . . . . . . . . . 5
2 Constructors: The Ceremony of Creation 8
2.1 The Problem of the Uninitialised Object . . . . . . . . . . . . . 8
2.2 Constructor Syntax and Semantics . . . . . . . . . . . . . . . . 8
2.3 The Default Constructor . . . . . . . . . . . . . . . . . . . . . . 9
2.4 Constructor Chaining with this() . . . . . . . . . . . . . . . . 10
3 The this Keyword: An Object Knowing Itself 12
3.1 The Identity Problem . . . . . . . . . . . . . . . . . . . . . . . . 12
3.2 The this Reference . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3 When this Is and Is Not Necessary . . . . . . . . . . . . . . . . 13
3.4 The Deep Mechanics: What Is this Really? . . . . . . . . . . . . 13
II Encapsulation
The Art of Controlled Access 15
4 Access Modifiers: The Philosophy of Privacy 16
4.1 The Open Window Problem . . . . . . . . . . . . . . . . . . . . 16
vi
CONTENTS
4.2 Access Modifiers in Java . . . . . . . . . . . . . . . . . . . . . . 16
4.3 Applying private to Our Player . . . . . . . . . . . . . . . . . . 17
4.4 The Subtle Question: Same-Class Access . . . . . . . . . . . . . 18
5 Getters and Setters: The Controlled Interface 20
5.1 The Paradox of Useful Privacy . . . . . . . . . . . . . . . . . . . 20
5.2 Getters: Read-Only Windows . . . . . . . . . . . . . . . . . . . 20
5.3 Setters: The Gatekeeper . . . . . . . . . . . . . . . . . . . . . . 21
5.4 The Level Setter: An Additional Example . . . . . . . . . . . . . 22
5.5 When Not to Use Getters and Setters . . . . . . . . . . . . . . . 22
III The Shared Reality
Static Members and Class-Level State 25
6 Static: What Belongs to the Blueprint 26
6.1 The Counter Problem . . . . . . . . . . . . . . . . . . . . . . . . 26
6.2 The static Keyword . . . . . . . . . . . . . . . . . . . . . . . . 27
6.3 Accessing Static Members . . . . . . . . . . . . . . . . . . . . . 27
6.4 Static Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6.5 The Golden Rules of Static Access . . . . . . . . . . . . . . . . . 28
6.6 Static Initialisation Blocks . . . . . . . . . . . . . . . . . . . . . 29
IV Under the Hood
The JVM’s Memory Universe 31
7 The Three Rooms of JVM Memory 32
7.1 Why Memory Architecture Matters . . . . . . . . . . . . . . . . 32
7.2 The Metaspace: The Library . . . . . . . . . . . . . . . . . . . . 32
7.3 The Heap: The Warehouse . . . . . . . . . . . . . . . . . . . . . 33
7.4 The Stack: The Notepad . . . . . . . . . . . . . . . . . . . . . . 33
7.5 Tracing a Single Line of Code . . . . . . . . . . . . . . . . . . . 34
7.6 Why Three Separate Areas? . . . . . . . . . . . . . . . . . . . . 35
8 The Object Lifecycle and Garbage Collection 36
8.1 The Lifecycle of an Object . . . . . . . . . . . . . . . . . . . . . 36
8.2 The Orphaned Object . . . . . . . . . . . . . . . . . . . . . . . . 36
vii
CONTENTS
8.3 The Garbage Collector . . . . . . . . . . . . . . . . . . . . . . . 37
8.4 Memory Leaks in Java . . . . . . . . . . . . . . . . . . . . . . . 38
8.5 The finalize() Method and Its Successors . . . . . . . . . . . 38
9 The Anatomy of an Object in Memory 40
9.1 What Happens When You Write new? . . . . . . . . . . . . . . . 40
9.2 The Object Header . . . . . . . . . . . . . . . . . . . . . . . . . 40
9.2.1 The Mark Word (8 bytes) . . . . . . . . . . . . . . . . . 40
9.2.2 The Class Pointer (4 bytes, with Compressed OOPs) . . . 41
9.3 Field Storage: Primitives vs. References . . . . . . . . . . . . . 42
9.3.1 Primitive Fields: Stored Directly . . . . . . . . . . . . . . 42
9.3.2 Reference Fields: Stored as Addresses . . . . . . . . . . 42
9.4 Object Layout Diagram . . . . . . . . . . . . . . . . . . . . . . . 43
10 Alignment, Padding, and the Physics of Memory 45
10.1 Why Alignment Exists . . . . . . . . . . . . . . . . . . . . . . . 45
10.2 Natural Alignment: The Golden Rule . . . . . . . . . . . . . . . 45
10.3 Internal Padding: The Swiss Cheese Problem . . . . . . . . . . . 46
10.4 JVM Field Reordering: The Smart Packer . . . . . . . . . . . . . 46
10.5 End Padding: The Object Size Rule . . . . . . . . . . . . . . . . 47
10.5.1 Reason 1: CPU Bus Alignment for Efficient Fetching . . . 47
10.5.2 Reason 2: Compressed OOPs – The Brilliant Hack . . . . 47
10.6 Alignment for Small Types . . . . . . . . . . . . . . . . . . . . . 48
10.7 A Complete Worked Example . . . . . . . . . . . . . . . . . . . 49
Conclusion 51
A Default Values in Java 53
B Java Primitive Types and Memory Sizes 54
C Glossary 55
D Further Reading 57
A Final Note 58
viii
Part I
The Architecture of Thought
Classes, Objects, and the Nature of Identity
1
CHAPTER 1
Blueprint and Instance: The Philosophy of
Class and Object
“The class is an idea. The object is a thing. The distance
between idea and thing is where engineering lives.”
— Anonymous
1.1 Why This Distinction Matters
Every domain of human knowledge eventually confronts the distinction be-
tween a category and an instance of that category. A recipe is not a meal. An
architectural blueprint is not a house. The word “dog” in the dictionary is not
the actual warm creature sleeping on your floor.
Object-oriented programming is built on exactly this distinction, and in Java
it is expressed through the duality of class and object. This is not merely a tech-
nical detail to memorize—it is the foundational conceptual move that makes
everything else in OOP possible: inheritance, polymorphism, encapsulation,
and the entire philosophy of modelling the world in software.
Begin with a question: what does it mean to describe a thing without having
any specific thing in mind?
1.2 The Class: A Blueprint
Class
A class in Java is a named template, or blueprint, that defines the structure
(fields) and behaviour (methods) shared by all objects of that type. A class
is not a value; it occupies no space in the Heap. It is a description, not a
thing.
Consider a video game. We know that every player in the game will have a
name, a level, and some amount of health. We also know that every player can
level up, take damage, and die. This knowledge—that all players share these
attributes and behaviours—is precisely what a class captures.
2
1.3. THE OBJECT: A LIVING INSTANCE
1 class Player {
2 // Fields : the structure ( what every player HAS )
3 String username ;
4 int level ;
5 int health ;
6
7 // Methods : the behaviour ( what every player CAN DO )
8 void levelUp () {
9 this . level = this . level + 1;
10 System . out . println ( this . username + " leveled up ! " ) ;
11 }
12
13 void takeDamage ( int amount ) {
14 this . health = this . health - amount ;
15 if ( this . health < 1) {
16 System . out . println ( " Game over ! " ) ;
17 }
18 }
19 }
Listing 1.1: A minimal Player class — the blueprint.
Notice what this class does not do: it does not say what any specific player’s
name is, or what their health value currently is. It says only that every player
will have a health value, and that this value can be changed by the takeDamage
method. The class is a pure description.
▶ Classes Live in the Metaspace
When Java loads your program, the class definition—the compiled byte-
code describing Player—is stored in an area of JVM memory called the
Metaspace. The class blueprint lives there for as long as the application
runs. Objects, by contrast, live in the Heap and come and go throughout
the program’s lifetime. We will explore this architecture in depth in Part IV.
1.3 The Object: A Living Instance
Object / Instance
An object (also called an instance) is a concrete realisation of a class,
created at runtime using the new keyword. Each object occupies a distinct
region of Heap memory and maintains its own copies of the class’s non-
static fields.
The moment we write new Player(), the JVM allocates a block of memory
on the Heap, populates it with the fields defined in the class, runs the construc-
tor, and hands us back a reference to that memory block. We can create as
many Player objects as we like, and each will be an independent entity.
3
CHAPTER 1. BLUEPRINT AND INSTANCE: THE PHILOSOPHY OF CLASS AND
OBJECT
1 public class Main {
2 public static void main ( String [] args ) {
3 Player p1 = new Player () ;
4 Player p2 = new Player () ;
5
6 p1 . username = " Ravi " ;
7 p1 . health = 100;
8
9 p2 . username = " Amit " ;
10 p2 . health = 80;
11
12 // Each object is independent
13 p1 . takeDamage (30) ; // Only p1 is affected
14 System . out . println ( p2 . health ) ; // Still 80
15 }
16 }
Listing 1.2: Creating objects from the blueprint.
p1 and p2 are two distinct objects, each with its own set of fields. Calling
takeDamage on p1 has no effect on p2 whatsoever. This independence is the
entire point of having separate instances.
Real-World Analogy: The Cookie Cutter
A cookie cutter is a class. Each cookie stamped out by that cutter is
an object. Every cookie has the same shape (defined by the cutter), but
each cookie can have different decorations (its field values). Breaking one
cookie has no effect on the others. The cutter itself is never consumed.
1.4 Fields: The Memory of an Object
Fields (sometimes called instance variables or member variables) are the pieces
of data that characterize a particular object. In our Player class, there are three
fields: username, level, and health.
Each field has a type and a name. The type constrains the kind of data that
field can hold:
Type Example Value Size in Memory Category
int 100 4 bytes Primitive
long 1234567890L 8 bytes Primitive
double 3.14 8 bytes Primitive
boolean true 1 byte (effective) Primitive
char 'A' 2 bytes Primitive
String "Ravi" 4 or 8 bytes (reference) Reference
Player p1 4 or 8 bytes (reference) Reference
4
1.5. METHODS: THE CAPABILITIES OF AN OBJECT
The distinction between primitive types and reference types is enormously
important for understanding memory layout, which we will revisit in Part IV.
For now, remember: primitives store their value directly; reference types store
an address pointing to where the actual data lives in the Heap.
1.5 Methods: The Capabilities of an Object
If fields answer the question “what does this object know?” then methods an-
swer “what can this object do?” Methods are named blocks of code associated
with a class. When called on an object, they operate on that object’s fields.
▶ Methods Are Shared; Fields Are Not
Here is a subtle and important point: when you create two Player ob-
jects, each object gets its own copy of the fields. But the method code is
not duplicated. Both objects share the same method code stored in the
Metaspace, and the JVM uses the calling object’s reference (this) to know
which object’s fields to operate on.
This means that 10 000 Player objects share exactly one copy of the
takeDamage method—an enormous memory saving.
1.6 Putting It Together: A Complete First Example
Let us write a complete, self-contained program that demonstrates the class
and object relationship in action, along with a small game simulation:
1 class Player {
2 String username ;
3 int level ;
4 int health ;
5
6 void levelUp () {
7 this . level = this . level + 1;
8 System . out . println ( username + " is now level " +
level + " ! " ) ;
9 }
10
11 void takeDamage ( int amount ) {
12 this . health = this . health - amount ;
13 if ( this . health < 1) {
14 System . out . println ( username + " has died . Game
over ! " ) ;
15 } else {
16 System . out . println ( username + " has " + health +
" HP remaining . " ) ;
17 }
18 }
19
5
CHAPTER 1. BLUEPRINT AND INSTANCE: THE PHILOSOPHY OF CLASS AND
OBJECT
20 void printStatus () {
21 System . out . println ( " --- " + username + " ---" ) ;
22 System . out . println ( " Level : " + level ) ;
23 System . out . println ( " Health : " + health ) ;
24 }
25 }
26
27 public class Main {
28 public static void main ( String [] args ) {
29 // Create two independent players
30 Player hero = new Player () ;
31 hero . username = " Ravi " ;
32 hero . level = 1;
33 hero . health = 100;
34
35 Player villain = new Player () ;
36 villain . username = " CheaterX " ;
37 villain . level = 50;
38 villain . health = 500;
39
40 hero . printStatus () ;
41 villain . printStatus () ;
42
43 hero . levelUp () ;
44 villain . takeDamage (100) ;
45 hero . takeDamage (120) ; // Hero dies
46 }
47 }
Listing 1.3: A small game demonstrating class and object interaction.
△ Fields Are Not Shared Between Objects
A persistent misconception among beginners is that changing a field in
one object somehow affects other objects of the same class. It does
not. Each object is a fully independent entity. [Link] = 100 and
[Link] = 500 are two completely separate memory locations.
Changing one never touches the other.
⊛ Exercises
1. (Conceptual) What is the fundamental difference between a class
and an object? Use an analogy from everyday life that is different
from the ones used in this chapter.
2. (Conceptual) If methods are shared among all objects of a class but
fields are not, what does the JVM need to know when it executes
[Link](30) to ensure it operates on hero’s health and not
villain’s? (Hint: think about what additional piece of information
must be passed silently to the method.)
6
1.6. PUTTING IT TOGETHER: A COMPLETE FIRST EXAMPLE
3. (Analytical) In the code listing above, how many objects are created?
How many class definitions are loaded? Where does each live in
memory?
4. (Implementation) Design a BankAccount class with fields
accountHolder (String), balance (double), and accountNumber
(int). Add methods deposit(double amount), withdraw(double
amount), and printStatement(). Create two account objects in a
Main class and simulate a transaction between them.
5. (Open-Ended) Consider a class called Temperature. What fields and
methods would it need? Could the same data be represented in mul-
tiple ways (Celsius, Fahrenheit, Kelvin)? How would you design the
class to handle unit conversion cleanly?
✓ Chapter Summary
• A class is a blueprint: it describes structure (fields) and behaviour
(methods) without holding any specific data.
• An object is a concrete instance of a class, created with new, residing in
the Heap, holding its own field values.
• Fields are per-object; each instance has independent copies.
• Methods are shared; all instances of a class share one copy of each
method’s bytecode in the Metaspace.
• The new keyword triggers memory allocation, field initialisation, and
constructor execution.
7
CHAPTER 2
Constructors: The Ceremony of Creation
“A well-designed constructor makes the invalid state of an
object impossible to express.”
— Paraphrasing Joshua Bloch, Effective Java
2.1 The Problem of the Uninitialised Object
In Chapter 1, we created Player objects by writing:
1 Player hero = new Player () ;
2 hero . username = " Ravi " ;
3 hero . level = 1;
4 hero . health = 100;
This works, but it is fragile. What happens if someone writes new Player()
and then forgets to set the health field? In Java, numeric fields default to zero,
so the player would start with health = 0, which is already dead. The object
would be born in an invalid state.
A good class should make it impossible to create an invalid object. This is
the purpose of the constructor.
Constructor
A constructor is a special method that is automatically called when an
object is created with new. It has the same name as the class, has no return
type, and is responsible for putting the new object into a valid, usable
initial state.
2.2 Constructor Syntax and Semantics
A constructor looks almost like a method, with two key differences: it shares
the class’s name exactly, and it declares no return type (not even void).
1 class Player {
2 String username ;
3 int level ;
4 int health ;
8
2.3. THE DEFAULT CONSTRUCTOR
5
6 // Constructor : called automatically by ' new Player (" Ravi
" , 5) '
7 Player ( String username , int level ) {
8 this . username = username ; // Set name from argument
9 this . level = level ; // Set level from argument
10 this . health = 100; // Always start at full
health
11 }
12
13 void levelUp () {
14 this . level ++;
15 System . out . println ( this . username + " leveled up ! " ) ;
16 }
17 }
Listing 2.1: Adding a parameterised constructor to Player.
Now, creating an invalid player is much harder:
1 Player p1 = new Player ( " Ravi " , 5) ; // Valid : health = 100
automatically
2 Player p2 = new Player ( " Amit " , 10) ; // Valid
3
4 // This line no longer compiles - - - you MUST provide name and
level :
5 // Player p3 = new Player () ; // COMPILE ERROR
The constructor acts as a gatekeeper at birth. It ensures that the object is
born with everything it needs to function.
2.3 The Default Constructor
▶ The Invisible Default Constructor
When you write a class with no constructor at all, Java silently provides
one for you: the default (no-argument) constructor. It does nothing
except call the parent class’s constructor and initialise all fields to their
default values (0, false, null).
The moment you define any constructor of your own, Java withdraws this
free default constructor. If you still want a no-argument constructor along-
side your parameterised one, you must write it explicitly.
1 class Player {
2 String username ;
3 int level ;
4 int health ;
5
6 // Default constructor ( no arguments )
7 Player () {
9
CHAPTER 2. CONSTRUCTORS: THE CEREMONY OF CREATION
8 this . username = " Unknown " ;
9 this . level = 1;
10 this . health = 100;
11 }
12
13 // Parameterised constructor
14 Player ( String username , int level ) {
15 this . username = username ;
16 this . level = level ;
17 this . health = 100;
18 }
19 }
Listing 2.2: Providing both a default and a parameterised constructor.
This technique—providing multiple constructors that differ in their param-
eter lists—is called constructor overloading.
2.4 Constructor Chaining with this()
There is a useful pattern where one constructor delegates to another to avoid
duplicating initialisation logic:
1 class Player {
2 String username ;
3 int level ;
4 int health ;
5
6 Player ( String username , int level ) {
7 this . username = username ;
8 this . level = level ;
9 this . health = 100;
10 }
11
12 // Delegates to the 2 - argument constructor
13 Player ( String username ) {
14 this ( username , 1) ; // " Start at level 1"
15 }
16 }
Listing 2.3: Constructor chaining: one constructor calls another.
The call this(username, 1) must be the first statement in the constructor
body. This ensures the primary initialisation logic is written exactly once.
△ Constructors Do Not Construct Alone
The new keyword does several things before your constructor code even
runs: it allocates memory on the Heap, it zero-initialises all fields (setting
numbers to 0, references to null), and then it invokes the constructor. By
the time the first line of your constructor executes, the object already exists
10
2.4. CONSTRUCTOR CHAINING WITH THIS()
in memory—your constructor is merely configuring it, not creating it from
nothing.
⊛ Exercises
1. (Conceptual) Why does Java withdraw the default constructor the
moment you define your own? What problem does this behaviour
prevent?
2. (Analytical) What are the field values of a Player object created
with new Player() if no constructor is explicitly defined? Which
Java specification guarantees this?
3. (Implementation) Add a third constructor to the Player class
that accepts only a level argument, sets a default username of
"Player_1", and delegates to the two-argument constructor using
this().
4. (Implementation) Design a Rectangle class with fields width and
height. Provide: (a) a constructor that accepts both dimensions, (b)
a constructor that creates a square (one dimension), using construc-
tor chaining.
5. (Open-Ended) Consider the following design question: should a
Player object be immutable after construction (no setters, all fields fi-
nal), or mutable (fields can change during gameplay)? What are the
trade-offs of each approach in the context of a multiplayer game?
✓ Chapter Summary
• A constructor is called automatically by new and is responsible for plac-
ing the object in a valid initial state.
• If you define no constructor, Java provides a silent default constructor.
Once you define any constructor, this default is withdrawn.
• Constructor overloading allows multiple constructors with different
parameter lists.
• Constructor chaining (this(...)) delegates to another constructor to
avoid code duplication.
• Memory allocation, zero-initialisation, and constructor execution hap-
pen in sequence; the constructor configures a pre-allocated object.
11
CHAPTER 3
The this Keyword: An Object Knowing It-
self
“Self-knowledge is the beginning of wisdom.”
— Socrates
3.1 The Identity Problem
Suppose you have a constructor like this:
1 Player ( String username , int level ) {
2 username = username ; // Does anything useful happen here
?
3 level = level ;
4 health = 100;
5 }
The lines username = username and level = level are meaningless. Java
sees two things with the same name in scope: the parameter username and the
field username. The parameter “shadows” the field, and the assignment simply
sets the parameter to itself. The field is never touched.
This is the identity problem: how does an object refer to itself, unambigu-
ously, in code?
3.2 The this Reference
this
this is an implicit reference available inside any non-static method or
constructor. It refers to the object on which the method was called—the
“current object.” Using [Link] unambiguously refers to the field
of the current object, bypassing any local variable or parameter with the
same name.
With this, the constructor becomes clear and correct:
1 Player ( String username , int level ) {
12
3.3. WHEN THIS IS AND IS NOT NECESSARY
2 this . username = username ; // " MY username " = the
parameter
3 this . level = level ; // " MY level " = the
parameter
4 this . health = 100; // " MY health " = 100 always
5 }
Listing 3.1: Using this to resolve name shadowing.
Now [Link] unambiguously means “the username field of the cur-
rent object,” and username on the right side means “the username parameter.”
The assignment is correct.
3.3 When this Is and Is Not Necessary
this is required when a local variable or parameter has the same name as a
field (name shadowing). It is optional but often used as a style convention to
make field access explicit:
1 void levelUp () {
2 // These two lines are equivalent when there is no
shadowing :
3 level ++; // Implicit : the compiler finds ' level '
as a field
4 this . level ++; // Explicit : clearly a field reference
5 }
Many professional Java developers prefer [Link] throughout for
clarity, especially in large classes where distinguishing fields from local vari-
ables by sight is valuable.
3.4 The Deep Mechanics: What Is this Really?
▶ The Hidden Parameter
When the JVM calls [Link](), it secretly translates this into some-
thing like [Link](p1). The reference p1 is passed as a hidden
first argument to the method. Inside the method, the JVM makes this hid-
den argument available as this. This is why this always refers to the
exact object the method was called on, and why it does not exist in static
methods (which are not called on any particular object and therefore have
no hidden argument).
13
CHAPTER 3. THE THIS KEYWORD: AN OBJECT KNOWING ITSELF
⊛ Exercises
1. (Conceptual) Explain in your own words why username = username
in a constructor is a no-operation. Draw the two “name slots” (the
field and the parameter) and show how the compiler resolves the
name without this.
2. (Analytical) Is this available inside a static method? Why or why
not? What error does Java produce if you try to use it?
3. (Implementation) Rewrite the BankAccount class from Chapter 1’s
exercises. Use this consistently throughout, ensuring all field ac-
cesses are qualified with this.
4. (Open-Ended) Some programming languages (Python, for example)
make the equivalent of this an explicit parameter called self that
appears in every method signature. Java hides it. What are the
advantages and disadvantages of each approach from a readability
and teaching perspective?
✓ Chapter Summary
• this is an implicit reference to the current object, available in all non-
static methods and constructors.
• It is required when a field and a local variable share the same name
(name shadowing).
• It is optional but useful as a style convention for clarity.
• Mechanically, this is the hidden first argument passed to every instance
method by the JVM.
• this does not exist in static methods because static methods are not
called on a specific object.
14
Part II
Encapsulation
The Art of Controlled Access
15
CHAPTER 4
Access Modiers: The Philosophy of Pri-
vacy
“A system is secure not when its walls are high, but when its
interfaces are small.”
— Adapted from Security Engineering
4.1 The Open Window Problem
Recall our Player class from Chapter 1. All its fields—username, level, health—
are declared with no access modifier, making them accessible to any code that
holds a reference to the object.
This creates a critical vulnerability. Imagine your game has been released
and attracts thousands of players. A clever user discovers that they can write:
1 // CheaterX 's script
2 Player cheater = new Player ( " CheaterX " , 10) ;
3 cheater . health = 1 _000_000 ; // Bypass all game mechanics
4 cheater . level = 9 _999 ; // Instant max level
Listing 4.1: A malicious script exploiting unprotected fields.
Your takeDamage method, with all its careful logic, is completely bypassed.
The object’s internal state is modified directly, from the outside, without any
validation whatsoever. The game is broken.
This is the open window problem: your class has a locked front door (meth-
ods with logic) but all its windows are wide open (fields accessible directly).
4.2 Access Modiers in Java
Java provides access modifiers—keywords placed before a field or method dec-
laration that control which other code is allowed to see and interact with it.
16
4.3. APPLYING PRIVATE TO OUR PLAYER
Modifier Who Can Access Typical Use
public Everyone, everywhere Methods meant to be called externally
private Only code inside the same class Fields; implementation details
protected Same class + subclasses + same package Inheritance scenarios
(none) Same package only Package-internal helpers
For now, the critical pair is public and private.
private
Declaring a field or method private makes it invisible to any code outside
the class in which it is defined. Only methods within that exact class can
access it.
4.3 Applying private to Our Player
1 class Player {
2 private String username ; // Locked
3 private int level ; // Locked
4 private int health ; // Locked
5
6 Player ( String username , int level ) {
7 this . username = username ;
8 this . level = level ;
9 this . health = 100;
10 }
11
12 void takeDamage ( int amount ) {
13 this . health -= amount ;
14 if ( this . health < 1) {
15 System . out . println ( username + " is defeated ! " ) ;
16 }
17 }
18 }
Listing 4.2: Locking the fields with private.
Now the cheater’s script fails at compile time:
1 cheater . health = 1 _000_000 ; // COMPILE ERROR : ' health ' has
private access
The compiler refuses to build the program. The cheat is impossible—not
just hard, but structurally impossible. This is the power of encapsulation: it
moves correctness guarantees from runtime hope to compile-time enforcement.
17
CHAPTER 4. ACCESS MODIFIERS: THE PHILOSOPHY OF PRIVACY
4.4 The Subtle Question: Same-Class Access
A common point of confusion arises here: if two objects are both of type Player,
can one access the other’s private fields?
The answer is yes—and this is intentional and safe. The private access
modifier is enforced at the class level, not the object level. Code written inside
the Player class can access private fields on any Player instance:
1 class Player {
2 private int health ;
3
4 // Compares this player 's health with another 's
5 boolean isHealthierThan ( Player other ) {
6 return this . health > other . health ; // Allowed !
7 }
8 }
Listing 4.3: Same-class access to private fields.
This is safe because you wrote the Player class. A cheater cannot open
your compiled [Link] file and add new code to it. They can only write
code in their own files, which are outside the class—and therefore blocked by
private.
▶ Privacy Is a Class-Level Concept
In Java, privacy is enforced per class definition, not per object identity. The
security boundary is the class boundary. All code you write inside the class
is trusted; all code written in other classes is not. A hacker cannot inject
code into your class’s boundary without recompiling it.
⊛ Exercises
1. (Conceptual) Explain the difference between public and private
using an analogy involving a building. What physical structures cor-
respond to each modifier?
2. (Analytical) The following code attempts to print a player’s health.
Identify all lines that would fail to compile after marking health as
private:
1 Player p = new Player ( " Ravi " , 5) ;
2 System . out . println ( p . health ) ;
3 p . health = 50;
4 p . takeDamage (10) ;
3. (Implementation) Mark all three fields of Player as private. Run
the existing Main class. Fix every compile error you encounter by
18
4.4. THE SUBTLE QUESTION: SAME-CLASS ACCESS
working within the constraints of access modifiers.
4. (Open-Ended) Some frameworks (like certain ORMs and serialisa-
tion libraries) require fields to be public to function. How does this
create tension with the principle of encapsulation? What design pat-
terns are used to resolve this tension?
✓ Chapter Summary
• Access modifiers control which code can see a field or method.
• private restricts access to code within the same class.
• public allows access from anywhere.
• Making fields private prevents external code from directly manipulat-
ing object state, enforcing correctness at compile time.
• Same-class access to private fields is allowed and safe, because the
class author controls all code within the class boundary.
19
CHAPTER 5
Getters and Setters: The Controlled Inter-
face
“Never give up control of what you can control.”
— Unknown
5.1 The Paradox of Useful Privacy
After marking all fields as private, we face a new problem. The game’s user
interface needs to display the player’s health on screen. But [Link]
is now inaccessible. How do we let the outside world read the value without
letting it change the value?
The answer is to build a one-way glass window: a getter.
5.2 Getters: Read-Only Windows
Getter Method
A getter is a public method that returns the value of a private field. It
gives external code the ability to read internal state without the ability to
modify it.
1 class Player {
2 private String username ;
3 private int level ;
4 private int health ;
5
6 Player ( String username , int level ) {
7 this . username = username ;
8 this . level = level ;
9 this . health = 100;
10 }
11
12 // Getters : the read - only windows
13 public String getUsername () { return this . username ; }
14 public int getLevel () { return this . level ; }
15 public int getHealth () { return this . health ; }
20
5.3. SETTERS: THE GATEKEEPER
16 }
Listing 5.1: Getter methods for the Player class.
Now the UI can display health without any write access:
1 System . out . println ( player . getHealth () ) ; // OK : reads via
the getter
2 player . health = 500; // COMPILE ERROR :
still private
The naming convention get + field name (camelCase) is the official Jav-
aBeans standard, and it is universally followed. Many frameworks, IDEs, and
tools rely on this convention.
5.3 Setters: The Gatekeeper
For fields that legitimately need to change after construction, we provide a
setter. Unlike raw field access, a setter can include validation logic— a guard—
that rejects invalid values before they corrupt the object’s state.
Setter Method
A setter is a public method that accepts a value and, after optional vali-
dation, assigns it to a private field. It is the only legitimate path through
which external code can modify internal state.
Consider protecting the username with validation rules: it cannot be empty
and cannot exceed ten characters.
1 public void setUsername ( String newName ) {
2 // Guard : reject invalid names before touching the field
3 if ( newName == null || newName . isEmpty () ) {
4 System . out . println ( " Error : Username cannot be empty . "
);
5 return ;
6 }
7 if ( newName . length () > 10) {
8 System . out . println ( " Error : Username cannot exceed 10
characters . " ) ;
9 return ;
10 }
11 // If we reach here , the name is valid
12 this . username = newName ;
13 System . out . println ( " Username updated to : " + newName ) ;
14 }
Listing 5.2: A setter with guard logic for username validation.
1 player . setUsername ( " Ravi " ) ; // OK
21
CHAPTER 5. GETTERS AND SETTERS: THE CONTROLLED INTERFACE
2 player . setUsername ( " " ) ; // Error : Username cannot
be empty .
3 player . setUsername ( " APlayerWithAVeryLongName " ) ; // Error :
too long
The hacker cannot bypass this gate. The only way to change the username
is through setUsername, which will always enforce our rules.
5.4 The Level Setter: An Additional Example
1 public void setLevel ( int newLevel ) {
2 if ( newLevel < 1) {
3 System . out . println ( " Error : Level cannot be less than
1. " ) ;
4 return ;
5 }
6 if ( newLevel > 100) {
7 System . out . println ( " Error : Level cannot exceed 100. " )
;
8 return ;
9 }
10 this . level = newLevel ;
11 }
Listing 5.3: Setter with range validation for level.
5.5 When Not to Use Getters and Setters
△ Getters and Setters Are Not Always the Right Answer
A common anti-pattern is to generate getters and setters for every field
automatically, which reduces encapsulation to a mere naming ceremony.
If you expose every field through a getter/setter pair, you have effectively
made it public—with extra steps.
The right question is: should external code be able to read or modify this
field at all? If the answer is no, provide neither. If the answer is “read
but not write,” provide only a getter. If the answer is “write but with
validation,” provide only a setter. Think deliberately about each field.
▶ The Encapsulation Trilogy
Encapsulation in Java follows a three-step pattern:
1. Hide the data: mark fields private.
2. Expose reads safely: provide public getters where needed.
22
5.5. WHEN NOT TO USE GETTERS AND SETTERS
3. Control writes: provide public setters with validation where modi-
fication is permitted.
This trilogy ensures that every piece of data in your object is only accessible
through paths you explicitly design and control.
⊛ Exercises
1. (Conceptual) Explain the difference between providing a getter for a
field and making the field public. Are they equivalent? Under what
circumstances might they seem equivalent but actually differ?
2. (Implementation) Complete the Player class by adding: a setter
for health that prevents health from going above 100 or below 0; a
getter for all three fields; a setter only for username (level should not
be modifiable after construction, only via levelUp()).
3. (Analytical) A developer argues: “I will just make all my fields
public and add validation inside every method that uses them. This
is equivalent to setters.” Identify at least two reasons why this argu-
ment fails.
4. (Implementation) Design a Circle class with a private field radius.
Provide a setter that rejects negative radii. Add getter methods
getRadius(), getArea(), and getCircumference(). Note that area
and circumference are computed—they are not stored fields. What
does this tell you about the relationship between getters and fields?
5. (Open-Ended) In functional programming, objects are typically im-
mutable: no setters, all fields set at construction and never changed.
What would this mean for our Player class? How would you
“change” a player’s health if objects were immutable?
✓ Chapter Summary
• A getter exposes a private field’s value for reading, without granting
write access.
• A setter provides a controlled path for modifying a private field, allow-
ing validation logic to be enforced.
• The naming convention getField() / setField(value) is the Jav-
aBeans standard and is used by many frameworks.
• Not every field needs a getter or setter; decide deliberately based on
what external code legitimately needs.
• Getters can expose computed values that have no corresponding stored
23
CHAPTER 5. GETTERS AND SETTERS: THE CONTROLLED INTERFACE
field (e.g., getArea() on a Circle).
24
Part III
The Shared Reality
Static Members and Class-Level State
25
CHAPTER 6
Static: What Belongs to the Blueprint
“Not all things are personal. Some things belong to the room.”
— Unknown
6.1 The Counter Problem
Suppose your game server needs to know how many players are currently on-
line. Your instinct might be to add a counter to the Player class:
1 class Player {
2 String username ;
3 int playerCount = 0; // Every player tracks the total
?
4
5 Player ( String username ) {
6 this . username = username ;
7 this . playerCount = this . playerCount + 1;
8 }
9 }
Listing 6.1: A naive but broken counter.
1 Player p1 = new Player ( " Ravi " ) ; // playerCount in p1 = 1
2 Player p2 = new Player ( " Amit " ) ; // playerCount in p2 = 1
3 Player p3 = new Player ( " Priya " ) ; // playerCount in p3 = 1
The counter never goes above 1. Why? Because playerCount is an instance
variable. Each object gets its own copy of the variable, starting at 0. When p1
is created, it increments its own copy to 1. When p2 is created, it starts from
its own fresh copy of 0 and increments to 1. The three objects are counting
themselves in isolation, each unaware of the others.
▶ The Wristwatch vs. The Wall Clock
An instance variable is like a wristwatch: every person carries their own,
and each watch shows its own time. A static variable is like the clock on
the wall of a classroom: there is only one, everyone in the room sees the
same value, and when one person updates it, everyone sees the change
instantly.
26
6.2. THE STATIC KEYWORD
6.2 The static Keyword
Static Variable
A static variable (also called a class variable) is declared with the static
keyword. There is exactly one copy of a static variable, shared among all
instances of the class. It is created when the class is first loaded and lives
for the duration of the application.
1 class Player {
2 private String username ;
3 private int health ;
4
5 // ONE copy , shared by ALL Player objects
6 private static int totalPlayers = 0;
7
8 Player ( String username ) {
9 this . username = username ;
10 this . health = 100;
11 Player . totalPlayers ++; // Increment the shared
counter
12 }
13
14 public static int getTotalPlayers () {
15 return Player . totalPlayers ;
16 }
17
18 public String getUsername () { return this . username ; }
19 public int getHealth () { return this . health ; }
20 }
Listing 6.2: Fixing the counter with static.
1 Player p1 = new Player ( " Ravi " ) ;
2 Player p2 = new Player ( " Amit " ) ;
3 Player p3 = new Player ( " Priya " ) ;
4
5 System . out . println ( Player . getTotalPlayers () ) ; // 3
Now the counter works correctly. When any Player is created, the single
shared totalPlayers counter in the class is incremented.
6.3 Accessing Static Members
Notice that we wrote [Link] rather than [Link].
This is best practice and carries important meaning:
• [Link] says: “this is a class-level variable.”
27
CHAPTER 6. STATIC: WHAT BELONGS TO THE BLUEPRINT
• [Link] implies: “this is my personal variable,” which is mis-
leading for a static field.
Both compile successfully (Java allows the latter for compatibility), but the
former is strongly preferred for clarity.
6.4 Static Methods
Just as a variable can be static, so can a method.
Static Method
A static method belongs to the class rather than to any specific object.
It can be called using the class name (e.g., [Link]())
without needing to create an instance first. Static methods cannot access
instance fields or the this reference.
6.5 The Golden Rules of Static Access
This is one of the most tested concepts in Java interviews. Understanding why
the rules are what they are matters more than memorising them.
Golden Rule 6.1 (Instance Method Static Variable). An instance method can
read and write static variables freely.
Reason: When an instance method runs, a specific object exists. That object can
look up at the class-level “wall clock” and read or change it. Static variables exist
before any objects are created, so they are always available.
Golden Rule 6.2 (Static Method Instance Variable). A static method cannot
access instance variables or use this.
Reason: A static method can run when no objects exist at all. If you called
[Link]() before creating any players, there is no object, no
this, and no instance fields to refer to. The compiler refuses because the question
“whose health?” has no answer.
1 class Player {
2 private String username ; // Instance variable
3 private static int totalPlayers ; // Static variable
4
5 // Instance method : CAN access both
6 public void showInfo () {
7 System . out . println ( this . username ) ; // OK
8 System . out . println ( Player . totalPlayers ) ; // OK
9 }
10
11 // Static method : can ONLY access static
12 public static void showTotal () {
28
6.6. STATIC INITIALISATION BLOCKS
13 System . out . println ( Player . totalPlayers ) ; // OK
14 // System . out . println ( this . username ) ; // COMPILE
ERROR
15 }
16 }
Listing 6.3: Illustrating the Golden Rules.
△ The Access Table
From \ To Static Member Instance Member
Instance method ✓ Allowed ✓ Allowed
Static method ✓ Allowed × Error
6.6 Static Initialisation Blocks
For complex static initialisation (loading configuration from a file, seeding a
random number generator, etc.), Java provides static initialisation blocks:
1 class GameServer {
2 private static String serverName ;
3 private static int maxPlayers ;
4
5 // Runs once when the class is first loaded
6 static {
7 serverName = " Asia - Pacific -01 " ;
8 maxPlayers = 1000;
9 System . out . println ( " Server initialised : " +
serverName ) ;
10 }
11 }
Listing 6.4: Static initialisation block.
The static block runs exactly once, the first time any code references the
class.
⊛ Exercises
1. (Conceptual) In your own words, explain why a static method can-
not access an instance variable. Use a scenario from real life (not
code) to make the argument.
2. (Analytical) The following class has four methods. For each one,
state whether it will compile and explain why:
1 class GameData {
2 private String mapName ;
3 private static int roundNumber = 0;
29
CHAPTER 6. STATIC: WHAT BELONGS TO THE BLUEPRINT
4
5 void methodA () { System . out . println ( mapName ) ; }
6 void methodB () { System . out . println ( roundNumber )
; }
7 static void methodC () { System . out . println (
mapName ) ; }
8 static void methodD () { System . out . println (
roundNumber ) ; }
9 }
3. (Implementation) Extend the Player class so that totalPlayers is
decremented when a player is “destroyed.” Since Java has garbage
collection and no explicit destructors, discuss how you would signal
that a player should be removed from the count.
4. (Implementation) Write a Counter utility class with: a static field
count, static methods increment(), decrement(), reset(), and
getCount(). Write a Main that tests all four methods.
5. (Open-Ended) What happens to the static totalPlayers counter if
the program is run on a server with multiple JVMs handling different
users? Is the counter still shared? What does this tell you about the
limits of using static for shared global state?
✓ Chapter Summary
• A static variable has exactly one copy, shared among all instances of
the class.
• A static method belongs to the class and can be called without creating
an object.
• Static variables are created when the class is first loaded and live for
the duration of the application.
• Instance methods can access both static and instance members.
• Static methods can only access static members—they have no this and
no instance context.
• Always access static members via the class name (e.g.,
[Link]) for clarity.
30
Part IV
Under the Hood
The JVM’s Memory Universe
31
CHAPTER 7
The Three Rooms of JVM Memory
“To understand a program, you must become both the
machine and the mind.”
— Alan Perlis (paraphrased)
7.1 Why Memory Architecture Matters
You have been writing Java for a while now without knowing much about
where your data actually lives. This is by design—Java’s most celebrated fea-
ture is its abstraction over memory management. You do not call malloc; you
do not call free. The JVM handles it all.
But this abstraction has a cost: when your application runs slowly, or con-
sumes too much memory, or throws an OutOfMemoryError, you are helpless
unless you understand what is happening underneath. More fundamentally,
understanding the JVM’s memory model is what transforms a competent coder
into a genuine engineer.
The JVM organises memory into several distinct areas. Three are essential
to understand: the Metaspace, the Heap, and the Stack.
7.2 The Metaspace: The Library
Metaspace
The Metaspace (called the Method Area in earlier JVM specifications) is
where the JVM stores class-level information: the bytecode of methods,
field names and types, static variables, and constant pools. It is populated
during class loading and persists for the lifetime of the application.
When Java first encounters the word Player in your code, it reads the
[Link] file from disk, parses it, and stores:
• The bytecode of all methods (takeDamage, levelUp, etc.)
• The names and types of all fields
32
7.3. THE HEAP: THE WAREHOUSE
• All static variables (like totalPlayers)
• The class’s metadata (its name, parent class, implemented interfaces)
This is called class loading, and it happens exactly once per class per JVM
instance.
7.3 The Heap: The Warehouse
Heap
The Heap is the region of JVM memory where all objects live. Every time
new is called, a block of contiguous memory is allocated on the Heap and
populated with the object’s field values. The Heap is shared among all
threads of the application.
The Heap is where the living, breathing Player objects reside—the actual
data. When you write new Player("Ravi", 5), a block of memory is allocated
on the Heap containing "Ravi", the level 5, and the health 100.
The Heap is large (configurable via JVM flags like -Xmx) and is managed
by the Garbage Collector—the background process that periodically reclaims
memory from objects that are no longer accessible.
7.4 The Stack: The Notepad
Stack
The Stack (more precisely, each thread has its own thread stack) stores
the local variables and parameters of currently-executing methods. Each
method invocation creates a stack frame that contains the method’s local
variables and the program counter (the current line of execution). When a
method returns, its frame is popped and its local variables are destroyed.
The Stack is where references to objects live. When you write:
1 Player p1 = new Player ( " Ravi " , 5) ;
. . . the variable p1 (a reference—think of it as a memory address) is stored
on the Stack, inside the current method’s frame. The object itself is on the Heap.
The Stack merely holds the address telling the JVM where to find the object.
33
CHAPTER 7. THE THREE ROOMS OF JVM MEMORY
7.5 Tracing a Single Line of Code
Let us trace the execution of this single line through all three memory areas:
1 Player p1 = new Player ( " Ravi " , 5) ;
Step 1: Class Loading (Metaspace)
The JVM encounters the word Player. It checks whether [Link]
is already loaded into the Metaspace. If not, it reads the .class file from
disk, parses it, and stores the blueprint in the Metaspace. The static field
totalPlayers is created here and set to its default value of 0.
Step 2: Heap Allocation (new)
new Player("Ravi", 5) triggers Heap allocation. The JVM calculates the
exact number of bytes needed for a Player object (including its header and
all fields), finds a contiguous block of that size on the Heap, zero-initialises all
fields, runs the constructor to set the field values ("Ravi", 5, 100), and returns
the memory address of the new object (say, 0x500).
Step 3: Reference Storage (Stack)
The JVM sees Player p1. It creates a local variable named p1 on the current
Stack frame. The value stored in p1 is the memory address 0x500—a reference
to the Heap object.
Stack Heap Metaspace
main() frame Player @ 0x500 [Link]
class ptr
header: ... totalPlayers: 2
p1 = 0x500
name ref: 0xA00
p2 = 0x800 level: 5 void levelUp() {...}
health: 100 void takeDamage(){...}
int getHealth() {...}
Player @ 0x800
header: ...
name ref: 0xB00
level: 10
health: 80
Figure 7.1: JVM Memory: Stack holds references; Heap holds objects; Metas-
pace holds class blueprints and static data.
34
7.6. WHY THREE SEPARATE AREAS?
7.6 Why Three Separate Areas?
The separation is a deliberate engineering decision:
• Stack is fast (LIFO push/pop, cache-friendly) but small and has a fixed,
bounded lifetime. Perfect for short-lived local variables.
• Heap is large and flexible, but slower to allocate from and requires com-
plex garbage collection. Perfect for objects with unpredictable or long
lifetimes.
• Metaspace holds class-level data that never changes at runtime. Keep-
ing it separate allows the JVM to apply different memory management
policies to it (no garbage collection needed for most class metadata).
⊛ Exercises
1. (Conceptual) Where does the static variable totalPlayers live—
Stack, Heap, or Metaspace? Why does it make sense for it to live
there?
2. (Analytical) When you call a recursive method, what happens to the
Stack? What error occurs if the recursion is too deep, and why?
3. (Analytical) If you create an array of 1000 Player objects, how many
objects exist on the Heap? (Hint: the array itself is an object.)
4. (Open-Ended) The Heap is shared between all threads of a multi-
threaded application; each thread has its own Stack. What con-
currency problem does this shared Heap create? How does the
synchronized keyword address it?
✓ Chapter Summary
• Metaspace: stores class blueprints, method bytecode, and static vari-
ables. Populated during class loading; lives for the application’s lifetime.
• Heap: stores all objects created with new. Managed by the Garbage
Collector.
• Stack: stores references (addresses) and local primitive variables within
method frames. Frames are created on method entry and destroyed on
method return.
• When new is called: (1) class is loaded into Metaspace if not already
there; (2) object is allocated on the Heap; (3) a reference to the object
is stored on the Stack.
35
CHAPTER 8
The Object Lifecycle and Garbage Collec-
tion
“All things that come into being pass away. The only question
is when, and who is responsible for the passing.”
— Adapted from Buddhist Philosophy
8.1 The Lifecycle of an Object
Every Java object goes through a definite lifecycle:
1. Allocation: new is called; memory is reserved on the Heap; fields are
zero-initialised.
2. Initialisation: the constructor runs; fields are set to meaningful values.
3. Use: the object is accessed through references; methods are called; fields
are read and written.
4. Unreachable: no references pointing to the object remain; it cannot be
accessed.
5. Collection: the Garbage Collector reclaims the object’s Heap memory.
Steps 1–3 are under your direct control. Steps 4–5 are managed by the
JVM.
8.2 The Orphaned Object
Consider this scenario:
1 public void createArmy () {
2 Player soldier = new Player ( " Soldier -1 " , 1) ;
3 // ... the method ends here
4 // ' soldier ' is a local variable ; it lives on the Stack
5 }
Listing 8.1: Creating and abandoning an object.
36
8.3. THE GARBAGE COLLECTOR
When createArmy() finishes:
• The Stack frame for createArmy() is popped. The local variable soldier
(the reference) is destroyed immediately.
• The Player object on the Heap is not destroyed immediately. It lingers,
occupying memory, with no reference pointing to it.
• It is now unreachable—no code in the entire program can access it, be-
cause the only reference to it has been destroyed.
An unreachable object is called an orphan. The Heap accumulates orphans
as your program runs. This is where the Garbage Collector steps in.
8.3 The Garbage Collector
Garbage Collector (GC)
The Garbage Collector is a background process within the JVM that peri-
odically identifies unreachable objects (garbage) and reclaims their Heap
memory for future allocations. Java’s GC is automatic: you do not, and
cannot, directly trigger or control it in normal code.
The GC’s core algorithm is built on a simple principle: an object is garbage
if it is unreachable from any “GC root.” GC roots include:
• Local variables on any thread’s Stack
• Static fields in the Metaspace
• References held by native (non-Java) code
If you can trace a chain of references from a GC root to an object, that object
is alive. If no such chain exists, the object is garbage and will be collected.
▶ Reachability Is Transitive
Suppose object A holds a reference to object B, and A is reachable from a
GC root. Then B is also reachable—not directly, but transitively. The GC
traces the entire object graph (a web of connected objects) to determine
reachability. Only the objects with no path from any root are collected.
37
CHAPTER 8. THE OBJECT LIFECYCLE AND GARBAGE COLLECTION
8.4 Memory Leaks in Java
△ Java Can Still Have Memory Leaks
A common misconception is that garbage collection makes memory leaks
impossible in Java. This is false. A memory leak occurs when objects
are logically no longer needed by the application but remain technically
reachable from a GC root, preventing collection.
The classic example: a static List that grows as you add objects but never
removes them. The objects in the list are still reachable (via the static
field), so the GC cannot collect them, and memory grows indefinitely.
1 class Cache {
2 // Static field : a GC root . Objects in this list can
never be collected .
3 private static List < Player > allPlayers = new ArrayList
< >() ;
4
5 public static void addPlayer ( Player p ) {
6 allPlayers . add ( p ) ;
7 // If we never call allPlayers . remove () , these Player
objects
8 // accumulate forever , even if the game no longer
uses them .
9 }
10 }
Listing 8.2: A memory leak: logically dead objects remain reachable.
8.5 The finalize() Method and Its Successors
Java once provided a finalize() method that the GC would call just before
collecting an object, allowing it to release non-memory resources (file handles,
database connections). This mechanism was deprecated in Java 9 and removed
in Java 18 due to deep design flaws (unpredictable timing, GC overhead, and
the possibility of “object resurrection”).
The modern replacement is [Link] (Java 9+), which pro-
vides reliable, predictable resource cleanup without the problems of finalize().
⊛ Exercises
1. (Conceptual) Explain the difference between an object being “un-
reachable” and an object being “deleted.” Why is there a delay be-
tween the two events?
2. (Analytical) After the following code runs, how many objects are
38
8.5. THE FINALIZE() METHOD AND ITS SUCCESSORS
eligible for garbage collection?
1 Player p1 = new Player ( " Ravi " , 1) ;
2 Player p2 = new Player ( " Amit " , 2) ;
3 Player p3 = p1 ;
4 p1 = null ;
5 p2 = null ;
3. (Open-Ended) Languages like C++ give the programmer explicit
control over memory: you allocate with new and must free with
delete. Java’s GC automates this. What are the trade-offs of each ap-
proach from the perspective of a game developer writing a real-time
game where consistent frame timing matters?
✓ Chapter Summary
• An object’s lifecycle: allocate → initialise → use → become unreachable
→ be collected.
• When a method ends, its Stack frame is destroyed, but Heap objects
remain until the GC runs.
• The GC collects objects that are unreachable from any GC root.
• Java can have memory leaks: objects that are logically unneeded but
remain technically reachable.
• finalize() is deprecated; use try-with-resources or
[Link] for resource management.
39
CHAPTER 9
The Anatomy of an Object in Memory
“If you wish to understand the universe, think in terms of
energy, frequency, and vibration. If you wish to understand
Java, think in terms of bytes.”
— Adapted, with apologies, from Nikola Tesla
9.1 What Happens When You Write new?
We have been saying “the JVM allocates memory on the Heap.” Let us now
open that black box and understand exactly what bytes are laid down, in what
order, and why.
When new Player("Ravi", 5) is executed, the JVM allocates a single con-
tiguous block of memory for the object. This block has three parts:
Part Contents Size (typical 64-bit JVM)
Object Header Mark Word + Class Pointer 12 bytes
Fields Instance field data variable
Padding Alignment filler 0–7 bytes
9.2 The Object Header
Every Java object, regardless of type or size, carries a header—a fixed block of
metadata that the JVM needs to manage the object. On a modern 64-bit JVM
with Compressed OOPs enabled, this header is 12 bytes.
The header contains two components:
9.2.1 The Mark Word (8 bytes)
The Mark Word is a chameleon: its meaning changes depending on the object’s
current state. It serves multiple purposes simultaneously by repurposing its bits
according to a tagging scheme.
40
9.2. THE OBJECT HEADER
State Mark Word Contents When
Unlocked Identity hashcode (31 bits) + age + tag Normal use
Biased Thread ID + epoch + age + tag Single-thread access
Lightweight lock Stack pointer to lock record + tag Low-contention locking
Heavyweight lock Pointer to monitor object + tag High-contention locking
GC Forwarding pointer + tag During GC copying
The Identity Hashcode. When you print an object without overriding toString(),
you see something like Player@3764951d. That hexadecimal number is the ob-
ject’s identity hashcode—a unique integer ID generated the first time [Link](
is called. This number is stored in the Mark Word.
Lock Information. The Mark Word also encodes the locking state of the ob-
ject. Every Java object can serve as a monitor (a mutual-exclusion lock) via
the synchronized keyword. The two lock-state bits in the Mark Word act as
a traffic light: green (unlocked—any thread can acquire it), yellow (biased—
one thread owns it cheaply), and red (contended—a queue of waiting threads
exists).
GC Age. A small portion of the Mark Word tracks how many garbage collec-
tion cycles this object has survived. Objects that survive many GC cycles are
“promoted” to an older generation of the Heap, where they are collected less
frequently.
9.2.2 The Class Pointer (4 bytes, with Compressed OOPs)
The second part of the header is a pointer back to the [Link] blueprint
in the Metaspace. This is how the JVM knows, for any arbitrary Heap address,
what type of object it is looking at—and therefore which methods and which
field layout apply to it.
▶ How Runtime Polymorphism Works
When the JVM executes a virtual method call, it follows the class pointer in
the object’s header to the Metaspace, finds the class’s method table (called
the vtable), looks up the correct method implementation, and dispatches
the call. This indirection is the mechanism behind polymorphism: the JVM
always uses the object’s actual type (found via the class pointer), not the
declared type of the reference variable.
41
CHAPTER 9. THE ANATOMY OF AN OBJECT IN MEMORY
9.3 Field Storage: Primitives vs. References
After the 12-byte header, the JVM lays out the object’s field data. Two funda-
mentally different strategies apply:
9.3.1 Primitive Fields: Stored Directly
Primitive fields (int, long, double, boolean, char, etc.) are stored directly
inside the object block. Their value occupies exactly their declared size:
Type Size Example in Player
int 4 bytes health, level
long 8 bytes (if used)
double 8 bytes (if used)
boolean 1 byte (if used)
char 2 bytes (if used)
This is efficient: no indirection, no extra allocation. The value 100 for
health is literally present as four bytes (0x00 0x00 0x00 0x64) in the object’s
memory block.
9.3.2 Reference Fields: Stored as Addresses
Reference fields (String, Player, arrays, any object type) cannot be stored di-
rectly in the parent object’s block because their size is variable and unpredictable—
a String might be 2 characters or 200,000.
Instead, the parent object stores a reference: a 4-byte memory address
(with Compressed OOPs) or 8-byte address (without) pointing to where the
referenced object lives on the Heap.
1 // Player has : String name ( reference ) + int health (
primitive )
2 // Layout in memory (12 - byte header , Compressed OOPs ) :
3 //
4 // Offset 0 -11: Object Header ( Mark Word + Class Pointer )
5 // Offset 12 -15: health (4 bytes , direct value )
6 // Offset 16 -19: name (4 bytes , reference = address of String
object )
7 // Offset 20 -23: Padding (4 bytes , to reach multiple of 8)
8 //
9 // Total : 24 bytes
Listing 9.1: Memory layout of a Player with two fields.
42
9.4. OBJECT LAYOUT DIAGRAM
△ String Is Not Stored Inside Player
A field of type String does not embed the string’s characters inside the
Player object. The Player object contains a 4-byte address pointing to a
separate String object on the Heap, which in turn contains a reference to
a char[] array holding the actual characters. There are at least two Heap
objects involved in storing a player’s name.
9.4 Object Layout Diagram
The following figure illustrates the byte-level layout of our Player object on a
64-bit JVM with Compressed OOPs.
Mark Word (identity hash, lock bits, GC age) Class Pointer (4 byte
Cls Ptr
Mark Word (8 bytes)
+0 +4 +8
Figure 9.1: Byte layout of a Player object with int health and String name,
on a 64-bit JVM with Compressed OOPs. Total: 24 bytes.
⊛ Exercises
1. (Analytical) A class has fields: long score, int level, boolean
alive. Calculate the minimum Heap size of a single object assum-
ing a 12-byte header and alignment to a multiple of 8. Show your
working.
2. (Conceptual) Why is a String field stored as a reference (address)
rather than embedding the characters directly in the object? What
would go wrong if we tried to embed them?
3. (Conceptual) What two pieces of information does the Mark Word
store that are relevant to: (a) putting an object in a HashMap, and (b)
using synchronized on an object?
4. (Open-Ended) Consider an int vs. an Integer (the wrapper class).
How does each store the value 42? Calculate the exact byte cost
of each on a 64-bit JVM. What does this tell you about the cost of
autoboxing?
43
CHAPTER 9. THE ANATOMY OF AN OBJECT IN MEMORY
✓ Chapter Summary
• Every Heap object has three regions: header, fields, and padding.
• The header (12 bytes typical) contains the Mark Word (hashcode, locks,
GC age) and the Class Pointer.
• Primitive fields are stored directly in the object block.
• Reference fields store only a 4-byte address pointing to another Heap
object.
• The Class Pointer enables the JVM to look up method dispatch tables,
powering polymorphism.
44
CHAPTER 10
Alignment, Padding, and the Physics of Mem-
ory
“There is no such thing as wasted space—only misallocated
purpose.”
— Unknown
10.1 Why Alignment Exists
Modern CPUs do not read individual bytes from memory. They read in words—
chunks of 4 or 8 bytes at a time. Furthermore, CPUs are most efficient when
the data they fetch is naturally aligned: an int (4 bytes) should start at an
address divisible by 4; a long (8 bytes) should start at an address divisible by
8.
When data is misaligned—when a 4-byte integer starts at an odd address—
the CPU must perform two memory reads and stitch the result together in soft-
ware. On some architectures, misaligned access raises a hardware exception.
Even on permissive architectures like x86, it is measurably slower.
Alignment is therefore not a quirk of Java. It is a requirement of the hard-
ware.
10.2 Natural Alignment: The Golden Rule
Golden Rule 10.1 (Natural Alignment). A field of size n bytes must start at a mem-
ory address that is divisible by n. This is called natural alignment.
Type Size Address must be divisible by
long, double 8 bytes 8
int, float 4 bytes 4
short, char 2 bytes 2
byte, boolean 1 byte 1 (fits anywhere)
Reference (Compressed OOPs) 4 bytes 4
45
CHAPTER 10. ALIGNMENT, PADDING, AND THE PHYSICS OF MEMORY
10.3 Internal Padding: The Swiss Cheese Problem
If you declare fields in an unfortunate order, alignment requirements force the
JVM to insert “holes” of wasted bytes between them. Consider:
1 class Bad {
2 byte a; // 1 byte
3 long b; // 8 bytes ( needs address divisible by 8)
4 byte c; // 1 byte
5 }
Listing 10.1: A class with alignment-unfriendly field order.
If laid out naively (in declaration order), after the 12-byte header:
• a is at offset 12: fits fine.
• b needs to start at a multiple of 8. Offset 13 is not divisible by 8. The
next valid offset is 16. Offsets 13–15 are wasted (3 bytes of padding).
• c is at offset 24: fits fine.
• End padding to reach a multiple of 8: offset 25, padded to 32.
Total: 12 + 3gap + 8 + 1 + 7end = 31 bytes... padded to 32.
But wait—the data is only 10 bytes (1 + 8 + 1). We are using 32 − 10 = 22
bytes of overhead and padding for a 10-byte payload. This is the Swiss Cheese
Problem: the object is full of holes.
10.4 JVM Field Reordering: The Smart Packer
The JVM (specifically the HotSpot JVM) does not lay out fields in declaration
order. Instead, it reorders them to minimise internal padding. The strategy is
largest first:
1. long / double (8 bytes)
2. int / float / references (4 bytes)
3. short / char (2 bytes)
4. byte / boolean (1 byte)
Applying this to our Bad class, the JVM reorders to: long b, byte a, byte
c. Layout after the 12-byte header:
• b at offset 16 (padded from 12 to align long to 8): 8 bytes.
46
10.5. END PADDING: THE OBJECT SIZE RULE
• a at offset 24: 1 byte.
• c at offset 25: 1 byte.
• End padding from 26 to 32: 6 bytes.
Total: 32 bytes. Same total—but now there are no holes inside the object, only
unavoidable end padding.
▶ Holes in the Middle vs. Holes at the End
Internal holes (Swiss cheese) are genuinely wasteful—they can never be
used for anything. End padding is tolerable; it may even be used if the class
is extended with a subclass that adds more fields. The JVM’s reordering
strategy ensures all holes are pushed to the end.
10.5 End Padding: The Object Size Rule
After all fields are laid out, the JVM computes the total object size and rounds
it up to the next multiple of 8. This is non-negotiable—it applies to every Java
object, regardless of how perfectly its fields are packed.
Why 20 Bytes Becomes 24
Suppose a Player object’s header (12) plus fields (8) totals 20 bytes. 20 is
not a multiple of 8. The next multiple of 8 is 24. So 4 bytes of padding are
appended, making the total 24 bytes.
This rule seems wasteful. Why enforce it? There are two compelling reasons.
10.5.1 Reason 1: CPU Bus Alignment for Ecient Fetching
If every object is guaranteed to start at a multiple of 8 bytes, the JVM can
guarantee that an object’s first few fields always fall within a single CPU cache
line. Misaligned objects could straddle two cache lines, doubling the memory
bandwidth required to access them.
10.5.2 Reason 2: Compressed OOPs The Brilliant Hack
This is the deeper and more elegant reason.
Standard 64-bit memory addresses take 8 bytes each. If every reference
in every object is 8 bytes, the memory overhead of a Java application roughly
doubles compared to a 32-bit program. An application that ran comfortably in
2 GB would suddenly need 4 GB just for its pointers.
The JVM engineers devised an elegant trick: Compressed Ordinary Object
Pointers (Compressed OOPs).
47
CHAPTER 10. ALIGNMENT, PADDING, AND THE PHYSICS OF MEMORY
The Key Observation. If every object starts at a multiple of 8, then the last
three bits of every object’s address are always zero. For example:
Address (decimal) Address (binary, last 6 bits)
0 ...000 000
8 ...000 1000
16 ...001 0000
24 ...001 1000
32 ...010 0000
The underlined three bits are always 000. They carry no information!
The Hack. Instead of storing the full 8-byte address, the JVM stores a com-
pressed 4-byte value, implicitly appending three zero bits when reading it back.
Real address = Compressed value × 8 (10.1)
Compressed value = Real address ÷ 8 (10.2)
With a 32-bit compressed pointer, the maximum value is 232 −1 = 4,294,967,295.
Multiplied by 8, this reaches 34,359,738,360 bytes—approximately 32 GB of
Heap. With plain 32-bit pointers, you could only address 4 GB.
The Price of the Hack. For this trick to work, every single object must start at
an address that is a multiple of 8. This is why end padding is non-negotiable:
it enforces the invariant that the JVM’s arithmetic relies on.
▶ Compressed OOPs: Eight Bytes Saved per Reference
With Compressed OOPs (enabled by default when the Heap is ≤ 32 GB),
every object reference takes 4 bytes instead of 8. In a typical Java applica-
tion with millions of object references, this halves the memory consumed
by references—often reducing total Heap usage by 20–40%.
The entire mechanism is invisible to you as a Java programmer. It is a
pure JVM engineering optimisation. But it explains why Java is so insistent
about that final end padding.
10.6 Alignment for Small Types
A common question is: do small types (byte, char, boolean) also have align-
ment requirements?
The answer follows directly from natural alignment: a byte (1 byte) must
start at an address divisible by 1—which is any address. A char (2 bytes) must
48
10.7. A COMPLETE WORKED EXAMPLE
start at an address divisible by 2—any even address. A boolean is 1 byte (even
though conceptually 1 bit): any address.
This is why small types are “easy” to pack: they fill any gap left over by
larger types. The JVM uses them as mortar between the bricks of larger fields.
10.7 A Complete Worked Example
Full Layout Calculation: GameCharacter
1 class GameCharacter {
2 boolean alive ; // 1 byte
3 long score ; // 8 bytes ( needs multiple of 8)
4 int level ; // 4 bytes
5 double speed ; // 8 bytes ( needs multiple of 8)
6 char initial ; // 2 bytes
7 }
JVM reorders: long score, double speed, int level, char initial,
boolean alive.
Layout (after 12-byte header):
Offset Field Type Size
12 (gap: align long to 16) padding 4
16 score long 8
24 speed double 8
32 level int 4
36 initial char 2
38 alive boolean 1
39 (end padding to multiple of 8) padding 1
Total 40 bytes
Data: 8 + 8 + 4 + 2 + 1 = 23 bytes. Overhead: 12 (header) + 5
(alignment+end padding) = 17 bytes.
⊛ Exercises
1. (Analytical) Calculate the total Heap size of an object with fields:
int x, byte b, long n, boolean flag, String name. Show the
JVM’s reordered layout and identify each byte.
2. (Analytical) A developer claims: “I can save memory by declaring all
my boolean fields as int so there’s no alignment waste.” Evaluate
this claim. Is it correct?
49
CHAPTER 10. ALIGNMENT, PADDING, AND THE PHYSICS OF MEMORY
3. (Mathematical) With Compressed OOPs using 32-bit references and
8-byte alignment, prove algebraically that the maximum addressable
Heap is 32 GB.
4. (Conceptual) Explain in plain English why the JVM’s end-padding
requirement is a necessary consequence of the Compressed OOPs opti-
misation, not an arbitrary design choice.
5. (Open-Ended) Java’s -XX:+UseCompressedOops flag enables Com-
pressed OOPs (default when Heap ≤ 32 GB). Above 32 GB, it is au-
tomatically disabled, and references become 8 bytes. You have an
application with a 28 GB Heap that needs to grow to 35 GB. What
counter-intuitive performance effect might you observe when cross-
ing the 32 GB boundary? Explain the memory arithmetic.
✓ Chapter Summary
• Natural alignment: a field of size n must start at an address divisible
by n. This is a hardware requirement for efficient CPU access.
• Internal padding (Swiss cheese): holes inserted between misaligned
fields. The JVM eliminates this by reordering fields largest-to-smallest.
• End padding: every Java object’s size is rounded up to a multiple of 8
bytes.
• End padding is mandated by Compressed OOPs, which uses 32-bit ref-
erences to address up to 32 GB of Heap by exploiting the invariant that
all objects start at 8-byte-aligned addresses.
• Small types (byte, boolean) have no alignment restriction and fill any
remaining gaps as “mortar.”
50
Conclusion: The Depth of a Single Line
“The more you know, the more you see in what you already
knew.”
— Unknown
Consider this line of Java—perhaps the simplest meaningful thing you can
write:
1 Player p1 = new Player ( " Ravi " , 5) ;
At the beginning of this book, this line was straightforward: you are making
a player. By now, you see the entire iceberg beneath the surface.
You see that Player is a class—a blueprint that lives in the Metaspace, defin-
ing the structure and behaviour of all players. You see that new triggers Heap
allocation, zero-initialisation, and constructor invocation in sequence. You see
that the resulting 24-byte block on the Heap begins with a 12-byte header con-
taining a Mark Word (holding an identity hashcode waiting to be generated,
and lock bits set to “unlocked”) and a Class Pointer (a compressed 4-byte ad-
dress back to [Link] in the Metaspace).
You see that the string "Ravi" is not inside that 24-byte block—it is a sepa-
rate object on the Heap, pointed to by a 4-byte reference stored at offset 16 of
the Player object. You see that the integer 5 is stored directly at offset 12, as
four bytes of value.
You see that p1—the variable—is a reference stored on the Stack, in the
current method’s frame, holding the 4-byte compressed address of the Heap
object. And you understand why that address is expressible in 4 bytes: because
every object starts at a multiple of 8, the last three bits of every address are
zero, allowing the JVM to implicitly append them and address 32 GB of Heap
with a 32-bit compressed pointer.
And you see the encapsulation invisible in the line: the fields are private,
reachable only through methods you designed. You see the static counter
totalPlayers incrementing in the Metaspace, shared across all instances, in-
creasing by one for this new arrival.
One line of code. Dozens of interlocking mechanisms. This is engineering.
The goal of this book has never been to give you facts to memorise. It has
been to cultivate a particular kind of attention: the habit of looking at the
surface of a thing and asking “but what is really happening?” That question,
asked persistently, is what separates a programmer who writes code from an
51
CHAPTER 10. ALIGNMENT, PADDING, AND THE PHYSICS OF MEMORY
engineer who understands systems.
You now understand one of the most fundamental systems in software: the
class-and-object model, from the philosophy of identity and structure, through
the engineering of encapsulation and access control, to the raw physics of bytes
and alignment in silicon.
There is much more to Java—inheritance, interfaces, generics, concurrency,
functional programming. But every one of those topics is built on the foun-
dation you have now mastered. Return to them with the same curiosity you
brought here. Ask the same question at every level.
But what is really happening?
52
CHAPTER A
Default Values in Java
When a field is declared but not explicitly initialised, Java assigns it a de-
fault value based on its type. This applies to instance fields and static fields but
not to local variables (which must be initialised before use).
Type Default Value
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0
char '\u0000' (null character)
boolean false
Any reference type null
The timing of default value assignment differs between instance and static
fields:
• Static fields: defaulted exactly once, when the class is first loaded into
the Metaspace.
• Instance fields: defaulted each time new is called, before the constructor
runs.
53
CHAPTER B
Java Primitive Types and Memory Sizes
Type Wrapper Class Size Range Notes
byte Byte 1 byte −128 to 127 Smallest integer
short Short 2 bytes −32,768 to 32,767 Rarely used
int Integer 4 bytes ±2,147,483,647 Default integer
long Long 8 bytes ±9.2 × 1018 Large integers
float Float 4 bytes ≈ ±3.4 × 1038 Single precision
double Double 8 bytes ≈ ±1.8 × 10308 Default floating-point
char Character 2 bytes '\u0000' to '\uFFFF' Unicode UTF-16
boolean Boolean 1 byte* true / false *JVM-dependent
∗ The Java Language Specification does not mandate the size of boolean. In
practice, HotSpot uses 1 byte for fields and 4 bytes when boolean is stored in
arrays (aligned for performance).
54
CHAPTER C
Glossary
Class A named blueprint defining the fields (data) and methods (be-
haviour) shared by all objects of that type. Stored in the Metas-
pace.
Object / Instance
A concrete realisation of a class, allocated on the Heap at run-
time via new. Each object has its own field values.
Constructor A special class member, named identically to the class, invoked
by new to initialise a newly allocated object.
this An implicit reference to the current object within non-static
methods and constructors.
Access Modifier
A keyword (public, private, protected, or none) controlling
the visibility of a class member.
Encapsulation
The OOP principle of hiding internal state (private fields) be-
hind a controlled public interface (methods).
Getter A public method that returns the value of a private field,
following the naming convention getFieldName().
Setter A public method that validates and assigns a value to a private
field, following the naming convention setFieldName(value).
static A keyword that makes a variable or method belong to the class
rather than to any specific object. Static members are shared
among all instances.
Metaspace The JVM memory area that stores class blueprints, method
bytecode, and static variables. Persists for the lifetime of the
application.
Heap The JVM memory area where all objects live. Managed by the
Garbage Collector.
Stack Per-thread JVM memory area storing method frames (local
variables and references) for currently executing methods.
55
APPENDIX C. GLOSSARY
Garbage Collector (GC)
A background JVM process that identifies unreachable objects
and reclaims their Heap memory.
Natural Alignment
The hardware requirement that a value of size n bytes start at
a memory address divisible by n.
Compressed OOPs
A JVM optimisation that stores object references as 4-byte com-
pressed values (instead of 8-byte raw addresses), allowing a
32-bit pointer to address up to 32 GB of Heap. Requires all
objects to be 8-byte aligned.
Object Header
The first 12 bytes (typical) of every Java object, containing the
Mark Word and the Class Pointer.
Mark Word An 8-byte multi-purpose header field storing identity hashcode,
locking state, and GC age.
Class Pointer
A 4-byte compressed reference (in the object header) pointing
to the class’s blueprint in the Metaspace.
Field Reordering
The JVM’s practice of laying out object fields in a different
order from their declaration, to minimise internal alignment
padding (largest-first strategy).
56
CHAPTER D
Further Reading
On Java Language and OOP Design
Bloch, J. (2018). Effective Java, 3rd Edition. Addison-Wesley. The definitive
guide to writing idiomatic, correct, and efficient Java. Every serious Java de-
veloper should read it cover-to-cover.
On JVM Internals and Performance
Evans, B., Gough, J., & Newland, C. (2018). Optimizing Java. O’Reilly Media.
Covers GC algorithms, JIT compilation, profiling, and memory architecture at
the engineering level.
On Memory Layout and JVM Object Model
Shipilëv, A. Java Object Layout (JOL) Tool. [Link]
A JVM tool that prints the exact byte layout of any Java object at runtime. In-
valuable for verifying the memory calculations in Part IV.
On Garbage Collection Algorithms
Jones, R., Hosking, A., & Moss, E. (2011). The Garbage Collection Handbook.
Chapman & Hall/CRC. The authoritative academic reference on GC theory and
practice.
On JVM Specification (Primary Source)
Lindholm, T., Yellin, F., Bracha, G., & Buckley, A. (2022). The Java Virtual Ma-
chine Specification, Java SE 19 Edition. Oracle. Free online at [Link]
On Object-Oriented Philosophy and Design Patterns
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns:
Elements of Reusable Object-Oriented Software. Addison-Wesley. The seminal
“Gang of Four” book that formalised object-oriented design patterns.
57
A Final Note
This book began with a student writing a video game character. It ended
with the student reasoning about compressed pointer arithmetic and 64-bit
JVM internals. That journey is not unusual—it is the natural arc of genuine
curiosity.
The best engineers are not those who memorise the most. They are those
who ask the most questions, who refuse to treat any abstraction as the final
word, who follow every explanation one level deeper until they hit physics.
You have now followed Java one level deeper than most programmers ever
go. Keep asking the next question.
58