Topic 52 of 119 6. Object-Oriented Programming in Java Interactive Tutorial

Objects in Java: Creation, State, Behavior, and Memory Layout

16 mins read Last updated: 22 Aug 2026 29,338 views

Direct Answer & Core Concept

An object in Java is three things at once: state held in fields, behavior provided by methods, and an identity that distinguishes it from every other object in the program. When you write new BankAccount(), three separate things happen in order. Memory for the object is allocated on the heap, the constructor runs to give that new object valid initial state, and the expression evaluates to a reference, not the object itself. The variable holds that reference. The important consequence is that Java separates the object from the name you gave it. You never manipulate an object directly, only through a reference, and copying that reference gives you two names for one object rather than two objects. Physically, a plain object is a header, its instance fields, and padding to round the total up to an 8-byte boundary. The header holds the class pointer, identity hash code, garbage collection age, and lock state. That is why every object costs more memory than its fields alone would suggest, and why a small object with no fields still occupies real space. This topic builds the vocabulary that the rest of section 6 assumes. Understanding that an object is a heap allocation plus a distinct identity is what makes later arguments, such as why equality needs equals, immediately obvious.

Detailed Explanation & Mechanics

1) The Three Parts of Every Object

Any object, whether it is a BankAccount or a String, combines the same three ingredients.

PartWhat it isWhere it lives
StateThe values it holds, stored in fieldsIn the object on the heap
BehaviorThe operations it supports, defined by methodsIn the class, not the object
IdentityWhat makes it distinct from every other objectIts address on the heap

Note that behavior does not belong to the object. All BankAccount instances share one copy of the methods, defined once in the class. If each object carried its own methods, memory use would explode and nothing could be overridden.

2) Creation: Three Steps in Order

Main.java
Run Online
BankAccount account = new BankAccount();

That single line is really three separate events, and keeping them apart explains most surprising behaviour later.

StepWhat happens
DeclarationA local variable named account of type BankAccount is created on the stack
Instantiationnew allocates heap memory and the constructor runs to set valid initial state
AssignmentThe reference value is copied into the variable

The variable lives on the stack and the object lives on the heap. The variable is not the object, and it is not a pointer in the C sense either. It holds a reference value, which behaves exactly like a primitive that happens to point somewhere.


Diagram showing a variable on the stack holding a reference to a BankAccount object on the heap
The variable and the object are separate. The variable holds a reference value that points at the heap allocation.

3) State: Fields Hold the Values

State is whatever the object needs to remember between calls. Fields are declared inside the class, and each object gets its own copy of every instance field.

Main.java
Run Online
public class BankAccount {
    private final String accountNumber;
    // identity of the account
    private long balanceInCents;
    // mutable state
    public BankAccount(String accountNumber) {
        this.accountNumber = accountNumber;
        this.balanceInCents = 0L;
    } public void deposit(long amountInCents) {
        if (amountInCents <= ZERO) {
            throw new IllegalArgumentException("Deposit must be positive");
        } balanceInCents += amountInCents;
    } public long getBalanceInCents() {
        return balanceInCents;
    }
}

Because balanceInCents is an instance field, every BankAccount has its own separate value. Two accounts opened on the same day share no balance, because each object has its own copy of the field.

Prefer long cents over double for money. Binary floating point cannot represent most decimal fractions exactly, so repeated additions on a double balance drift, and a comparison against an expected total eventually fails for no visible reason.

4) Behavior: Methods Act Through this

Methods are the operations an object supports. Inside an instance method, this refers to the object the method was called on, which is how deposit reaches the right account balance.

Main.java
Run Online
BankAccount first = new BankAccount("ACC-1001");
BankAccount second = new BankAccount("ACC-1002");
first.deposit(FIFTY);
second.deposit(TEN_THOUSAND);
// first.getBalanceInCents()  is 50
// second.getBalanceInCents() is 10000

The same method ran twice with two different results. Nothing about the method changed, only which object this pointed at, which is the whole point of the this keyword.

A static method has no this, because it is not tied to any object. If a method needs to reach object state, it must be an instance method.

5) Identity: Every Object Is Distinct

Two objects can hold identical state and still be different objects. equals compares state, while == compares identity, so these behave differently.

Main.java
Run Online
String first = new String("hello");
String second = new String("hello");
System.out.println(first.equals(second));
// true,  same contents
System.out.println(first == second);
// false, different objects

That == result surprises people every time, and there is a subtlety behind it: when you assign a string literal, the JVM looks in the string pool and reuses a matching instance. So two literals usually end up as the same object, while two new String calls never do.

ComparisonFirst and second aboveMeaning
first.equals(second)trueSame contents
first == secondfalseSame object identity
first == nullfalseSafe null check on any reference

Use equals whenever you mean equal content, which is almost always. Reserve == for comparing against null, where it is safe on any type including Integer.

6) Memory Layout: Header, Fields, Padding

Underneath, a plain object is three regions laid out contiguously in memory.

RegionContents
Object headerClass pointer, identity hash code, GC age, lock state
Instance dataYour fields, laid out to minimise padding
PaddingBytes added to round the total up to an 8-byte boundary


Diagram of the object header, instance data and padding regions making up a Java object in memory
Header, instance data, then padding. The header is why every object costs more than its fields alone.

The exact header layout depends on the JVM and whether it uses compressed pointers, so treat these numbers as illustrative rather than a guarantee. The shapes around them, however, are stable enough to rely on.

An object with no fields still occupies a header, so a list of a million empty marker objects costs far more than the list itself. This is why primitive arrays are dramatically cheaper than Integer arrays: the array holds eight bytes per element instead of a reference to a separately allocated object.

7) Two Objects, Two Names

Assigning a variable copies a reference, not the object. Both names then point at the same object, which is why mutating through one is visible through the other.

Main.java
Run Online
BankAccount account = new BankAccount("ACC-1001");
BankAccount alias = account;
// copies the reference, not the object
alias.deposit(TEN_THOUSAND);
System.out.println(account.getBalanceInCents());
// 10000, the same object
System.out.println(account == alias);
// true, identical identity

There is still only one object. If you need an independent copy you must construct one deliberately, either by hand or with the copy constructor covered in duplicating objects safely.

8) Lifetime: Reachability and Collection

An object is collected once no reachable reference points to it. The variable going out of scope does not destroy the object, it only removes one path to it.

Main.java
Run Online
public static void main(String[] args) {
    BankAccount temporary = new BankAccount("ACC-9999");
    temporary.deposit(FIFTY);
    // the object is now unreachable, so it is eligible for collection
}

Eligibility is not the same as collection. The garbage collector runs whenever the JVM decides to, which is why you should never rely on finalisation timing or expect an object to vanish at the end of a block. The memory management topic covers the collectors and the areas in detail.

Frequently Asked Questions

What exactly is an object in Java?


An object combines state stored in its own fields, behavior provided by methods defined in its class, and an identity that makes it distinct from every other object. The object itself lives on the heap. Any variable you write is only a reference value pointing at it, never the object itself.

What does the new keyword actually do?


It performs three steps in order. It allocates heap memory for the object, it invokes the matching constructor so the new object has valid initial state, and the whole expression then evaluates to a reference value. The variable receives that reference, not the object.

Where are variables stored and where are objects stored?


Local variables of a method live on the stack for the duration of that call, while objects live on the heap until they become unreachable and are collected. The reference value held by a stack variable is what connects the two. The full picture, including the metaspace, is covered in the memory management topic.

Why does == return false for two equal strings?


Because == compares identity rather than content, and the two objects were created separately so they are different objects. equals compares contents and returns true. When you assign string literals the JVM usually reuses one pooled instance, so two literals can be identical by accident, which is why == is worth avoiding outside null checks.

Does an object get destroyed when a variable goes out of scope?


No. Losing a reference only removes one path to the object. It becomes eligible for collection once no reachable reference points to it, and the collector may reclaim it much later, so you must never depend on an object disappearing at the end of a block or on finalisation timing.

Does assigning a variable create a copy of the object?


No. It copies the reference, so both names point at one object and mutating through either is visible through the other. To get an independent object you must construct a new one, either explicitly or with a copy constructor, and for deep copies of nested references you need more care.

Why does an object with no fields still use memory?


Every object carries a header holding the class pointer, identity hash code, garbage collection age, and lock state, and then padding to align the total. An object with no fields is essentially just that header, which is why large arrays of tiny objects cost so much more than primitive arrays of the same length.

Should I declare fields final?


Declare a field final whenever the value cannot change after construction, which is most of the time. A final field cannot be reassigned, which makes the object easier to reason about and lets the JVM optimise more aggressively. Reserve non-final fields for values that genuinely need to change after construction, and keep them private so changes pass through a method that validates them.

Runnable Code Example: Objects in Java

Java 17+
Main.java
Run Online
// Shows what an object actually is: state, behavior, identity, and lifetime.
public class ObjectsInJavaDemo {
    // A named constant, because a bare digit before a parenthesis trips some parsers
    private static final long ZERO = 0L;
    private static final long FIFTY = 50L;
    private static final long TEN_THOUSAND = 10_000L;
    // STATE: each object gets its own copy of every instance field
    public static class BankAccount {
        private final String accountNumber;
        private long balanceInCents;
        public BankAccount(String accountNumber) {
            if (accountNumber == null || accountNumber.isBlank()) {
                throw new IllegalArgumentException("Account number is required");
            } this.accountNumber = accountNumber;
            this.balanceInCents = ZERO;
        } // BEHAVIOR: acts on this object through the this keyword
        public void deposit(long amountInCents) {
            if (amountInCents <= ZERO) {
                throw new IllegalArgumentException("Deposit must be positive");
            } balanceInCents += amountInCents;
        } public void withdraw(long amountInCents) {
            if (amountInCents > balanceInCents) {
                throw new IllegalStateException("Insufficient funds in " + accountNumber);
            } balanceInCents -= amountInCents;
        } public long getBalanceInCents() {
            return balanceInCents;
        } public String getAccountNumber() {
            return accountNumber;
        }
    } // IDENTITY: two objects with identical state are still different objects
    public static void showIdentity() {
        BankAccount first = new BankAccount("ACC-1001");
        BankAccount second = new BankAccount("ACC-1001");
        System.out.println("same contents: " + first.getAccountNumber().equals(second.getAccountNumber()));
        System.out.println("same identity:  " +(first == second));
        // false
        BankAccount alias = first;
        System.out.println("alias identity: " +(alias == first));
        // true
    } // STATE IS PER OBJECT, not per class
    public static void showState() {
        BankAccount first = new BankAccount("ACC-1001");
        BankAccount second = new BankAccount("ACC-1002");
        first.deposit(FIFTY);
        second.deposit(TEN_THOUSAND);
        System.out.println(first.getAccountNumber() + " has " + first.getBalanceInCents());
        System.out.println(second.getAccountNumber() + " has " + second.getBalanceInCents());
    } // MUTATION IS SHARED, ASSIGNMENT IS NOT
    public static void showSharedMutation() {
        BankAccount account = new BankAccount("ACC-1001");
        BankAccount alias = account;
        alias.deposit(TEN_THOUSAND);
        // Both names address one object, so both see the deposit
        System.out.println("through account: " + account.getBalanceInCents());
        System.out.println("through alias:   " + alias.getBalanceInCents());
    } // AN OBJECT BECOMES UNREACHABLE, it is not destroyed by scope exit
    public static void showLifetime() {
        BankAccount temporary = new BankAccount("ACC-9999");
        temporary.deposit(FIFTY);
        System.out.println("created and used " + temporary.getAccountNumber());
        // temporary goes out of scope here, but nothing was destroyed.
        // The object is merely eligible for collection, and the JVM
        // decides when. Never depend on collection timing.
    } // WHY int[] IS CHEAPER THAN Integer[]
    public static void showMemoryCost() {
        final int count = 1000;
        int[] primitives = new int[count];
        // roughly 8 bytes per slot
        Integer[] wrappers = new Integer[count];
        // a reference plus a separate object
        BankAccount[] accounts = new BankAccount[count];
        for (int index = 0;
        index < count;
        index++) {
            wrappers[index] = Integer.valueOf(index);
            accounts[index] = new BankAccount("ACC-" + index);
        } System.out.println("int[] holds " + primitives.length + " values in one flat block");
        System.out.println("Integer[] holds " + wrappers.length + " references");
        System.out.println("BankAccount[] holds " + accounts.length + " references, each pointing at a separate object with a header");
    } // AN OBJECT WITH NO FIELDS STILL COSTS A HEADER
    public static class Marker {
    } public static void showHeaderCost() {
        Object withNothing = new Marker();
        System.out.println("empty object class: " + withNothing.getClass().getSimpleName());
        System.out.println("it still carries a header, an identity hash, and padding");
    } public static void main(String[] args) {
        showState();
        System.out.println();
        showIdentity();
        System.out.println();
        showSharedMutation();
        System.out.println();
        showLifetime();
        System.out.println();
        showMemoryCost();
        System.out.println();
        showHeaderCost();
        /*
            ACTUAL OUTPUT:

                ACC-1001 has 50
                ACC-1002 has 10000

                same contents: true
                same identity:  false
                alias identity: true

                through account: 10000
                through alias:   10000

                created and used ACC-9999

                int[] holds 1000 values in one flat block
                Integer[] holds 1000 references
                BankAccount[] holds 1000 references, each pointing at a separate object with a header

                empty object class: Marker
                it still carries a header, an identity hash, and padding

            THE SIX THINGS TO REMEMBER
            1) AN OBJECT IS STATE, BEHAVIOR, AND IDENTITY. The state is in
               the object, the behavior is shared in the class, and the
               identity is what makes it a distinct object.
            2) new DOES THREE THINGS. It allocates on the heap, it runs
               the constructor, and it yields a reference value. The
               variable receives the reference, never the object.
            3) VARIABLES ARE NOT OBJECTS. A local variable holds a
               reference on the stack. That is why reassigning the
               variable cannot change what the object contains.
            4) BEHAVIOR IS SHARED, STATE IS NOT. All accounts use the one
               copy of the method from the class, but each object has its
               own copy of every field.
            5) equals COMPARES CONTENT, == COMPARES IDENTITY. Two objects
               can hold identical state and still be different objects,
               so use equals except when checking against null.
            6) LOSING A REFERENCE IS NOT DESTRUCTION. An unreachable
               object becomes eligible for collection, and the JVM
               decides when, so never depend on that timing.
        */
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1: What are the three defining parts of a Java object?\nA1: State, which is the values held in its own fields; behavior, which is the operations provided by methods defined in its class; and identity, which is what distinguishes it from every other object in the program. Behavior is shared across all instances while state is private to each object.\nQ2: What does the new keyword do under the hood?\nA2: It performs three steps in order. It allocates heap memory for the object, it invokes the matching constructor so the new object starts with valid state, and the expression then evaluates to a reference value that gets copied into your variable. The variable never holds the object itself.\nQ3: Where are local variables stored compared to objects?\nA3: Local variables of a method live on the stack for the duration of that call, while the objects they reference live on the heap until they become unreachable and are collected. The reference value held by the stack variable is what connects the two, so clearing the variable removes a reference rather than the object.\nQ4: Why does == return false for two Strings with identical contents?\nA4: Because == compares identity while equals compares content. Two separately constructed Strings are different objects, so == gives false and equals gives true. When you assign string literals the JVM often reuses one pooled instance, so two literals can be identical by accident, which is why == should be used only for null checks.\nQ5: Does an object get destroyed when its variable goes out of scope?\nA5: No. Leaving scope only removes one reference. The object becomes eligible for collection once no reachable reference points at it, and the garbage collector decides when to reclaim it, so you must never rely on collection timing or on finalizers running at a particular moment.\nQ6: Does assigning a variable to another variable copy the object?\nA6: No. It copies the reference, so both names address one object and any mutation through either is visible through the other, and comparing them with == gives true. An independent object requires explicitly constructing a new one, and a deep copy additionally needs each nested reference copied too.\nQ7: Why does an object with no fields still consume memory?\nA7: Every object carries a header holding the class pointer, identity hash code, garbage collection age, and lock state, followed by padding to round the total to an 8-byte boundary. A fieldless object is essentially just that header, which is why a large array of tiny objects costs far more than a primitive array of the same length.\nQ8: Should fields be declared final wherever possible?\nA8: Yes, whenever the value cannot change after construction, which is most of the time. A final field cannot be reassigned, the object becomes easier to reason about, and the JVM can optimise more aggressively around it. Keep fields mutable only where a genuine post-construction change is needed, and keep those private so every change passes through a validating method.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Declare fields private, and final whenever the value cannot change after construction.\nGive every object a constructor that leaves it in a valid state, with no partially initialised objects.\nValidate arguments inside the constructor rather than trusting the caller.\nPrefer long cents over double for money, since binary floating point drifts under repeated addition.\nUse equals rather than == for any comparison that is not against null.\nCompare against null with ==, which is safe on every reference type including Integer.\nTreat a variable as a reference and never assume assigning one changes the object.\nCopy an object deliberately when you need independence, instead of assuming assignment did it.\nUse centimetres or a scale factor rather than relying on floating point for equality checks.\nKeep the public surface of a class small, since every public method is a promise you must maintain.\nFavour immutability, because a final field or a record removes a whole class of aliasing bugs.\nReach for System.identityHashCode only for debugging, since identity is not a business key.\nDo not depend on garbage collection timing, and do not use finalizers to release resources.\nClose resources explicitly, since collection order and timing are never guaranteed.\nMinimise object creation in hot loops, and prefer primitive arrays over boxed ones for large numeric data.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.