Course contents

Java classes and objects

What a class and an object really are: fields and methods, references versus objects, the object header and field layout on the heap, Class objects, when a class is loaded and initialised, and object identity.

FreeMust know for interviewsLesson14 min readWarm-upReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: a blueprint and the houses built from it
  2. Classes and objects under the hood
  3. Class objects and class loading
  4. Real world: identity and class loaders in production
  5. Advanced: header sizes, identity and the edge cases
  6. How to say it in the interview

🧱 What are classes and objects?

A class is a type you define: it declares what data each instance carries (fields) and what it can do (methods). An object is one instance of that class, a block of memory on the heap holding its own copy of those fields. For example, one Order class can produce 2 million Order objects during a sale, each with its own id and quantity, all sharing the same method code loaded once.

This sounds like day-one material, and it is. That's exactly why interviewers use it as a warm-up that quietly separates people. Anyone can say "a class is a blueprint". Fewer can tell you what a == b actually compares, how many bytes an empty object takes, what getClass() returns, or why a class's static block didn't run when they expected it to.

What you’ll learn

  • Explain the difference between a class (a type plus code, loaded once) and an object (state on the heap, created many times)
  • Tell a reference variable apart from the object it points to, and predict what == compares
  • Sketch an object's heap layout: mark word, klass pointer, fields and padding, and size a small object in bytes
  • Describe the java.lang.Class object and why getClass() returns the same instance for every object of a type
  • List what triggers class initialisation (<clinit>) and what does not, such as Order.class or new Order[10]
  • Define object identity, identity hash codes, and how class identity depends on the class loader

For this lesson, we'll focus on the class and object model itself: fields and methods, references versus objects, what an object looks like on the heap, the java.lang.Class object, the basics of class loading and initialisation, and identity.

Below the line (out of scope)

The 30-second answer

A class is a type: a template that declares fields and methods. It is loaded into the JVM once (per class loader), and its metadata and bytecode live in Metaspace. An object is an instance of that class, allocated on the heap with new. Every object starts with a header (a mark word for hash and lock state, plus a pointer to its class), followed by its instance fields, padded to 8 bytes. Variables never hold objects, they hold references, so == on two references asks "same object?", not "same contents?". Each loaded class is also represented by one java.lang.Class object, which is what getClass() and Order.class return. A class is initialised, running its static initialisers, on first active use, not when it is merely mentioned.

Interview radar

Asked very often: What is the difference between a class and an object, and what does == compare for two object references? Senior rounds probe: How big is an object in memory, what is in its header, and when exactly does a class get initialised? Production angle: The ClassCastException: com.shop.Order cannot be cast to com.shop.Order you get when two class loaders load the same class.

01Picture it first: a blueprint and the houses built from it

The classic analogy holds up well if you push it one step further. A class is an architect's blueprint for a house: rooms, doors, where the wiring goes. An object is one house built from it. Ten houses from the same blueprint each have their own furniture (their own field values), but they all follow the same plan (the same methods).

The step most people skip is the address. You never carry a house around in your pocket. You carry a slip of paper with its address, and that's what a Java variable is: a reference. Give the slip to a friend and you now both point at the same house. Paint the door through one slip and the other sees it painted.

The diagram below shows all three pieces: the class loaded once, objects on the heap, and references on the stack pointing at them.

Fig. 1

The Order class sits once in Metaspace with its fields and methods; two Order objects on the heap each hold their own id and quantity; on the stack, references a and b point to the first object and c points to the second

The Order class sits once in Metaspace with its fields and methods; two Order objects on the heap each hold their own id and quantity; on the stack, references a and b point to the first object and c points to the second

Notice three things:

  • One class, many objects. Method bytecode is stored once in the class metadata, not copied into each object.
  • Each object carries a pointer back to its class. That's how a call like order.total() finds the right code at run time.
  • a and b are two references to one object. There are three variables but only two objects.

02Classes and objects under the hood

Let's start with a small class you'd see in any order service.

public class Order {
    // fields: the state each object carries
    private final long id;
    private int quantity;
    private boolean paid;
    private String customer;

    public Order(long id, String customer) {
        this.id = id;
        this.customer = customer;
    }

    // methods: behaviour shared by every Order
    public void addItems(int n) { quantity += n; }
    public int quantity()       { return quantity; }
    public long id()            { return id; }
}

A class body holds members: fields (state), methods (behaviour), constructors (setup, which technically aren't members because they aren't inherited), initialiser blocks and nested types. Instance fields exist once per object; static fields exist once per class. Methods are never per object, only their data is.

References, not objects

Try it yourself first

Before reading on, predict the four printed lines.

Order a = new Order(1001, "alice");
Order b = a;
Order c = new Order(1001, "alice");

b.addItems(3);
System.out.println(a.quantity());                    // ?
System.out.println(a == b);                          // ?
System.out.println(a == c);                          // ?
System.out.println(a.getClass() == c.getClass());    // ?

The answers are 3, true, false, true. Notice:

  • Order b = a; copies the reference, not the object. Calling addItems through b changes the one object both variables point at.
  • a == c is false even though both orders hold identical data. For references, == compares identity (same object), never contents. Contents are what equals is for, if the class overrides it.
  • Both objects report the same Class object, which we'll come back to.

This is also why Java is pass-by-value even for objects: a method receives a copy of the reference, so it can mutate the object but can't make your variable point somewhere else. We covered that in Methods and why Java is always pass-by-value.

What new compiles to

new Order(1001, "alice") is one expression in Java but three steps in bytecode. Running javap -c on the caller shows:

new           #7   // class com/shop/Order      1. allocate, fields zeroed
dup                                             2. keep a copy of the reference
ldc2_w        #9   // long 1001
ldc           #11  // String alice
invokespecial #13  // Method Order."<init>":(JLjava/lang/String;)V   3. run constructor
astore_1                                        4. store reference in local a

The new instruction allocates memory and sets every field to its default (0, false, null). Only then does invokespecial run the constructor, which the JVM calls <init>. In HotSpot, the allocation itself is usually a pointer bump inside a thread-local allocation buffer (TLAB), so it costs a few nanoseconds.

What an object looks like on the heap

Try it yourself first

Before reading on, guess how many bytes one Order object takes on a 64-bit JVM with default settings. It has a long, an int, a boolean and a String reference.

Every HotSpot object starts with a header:

  • Mark word (8 bytes): the identity hash code once computed, GC age bits and lock state.
  • Klass pointer (4 bytes with compressed class pointers, the default): points to the class metadata in Metaspace.

Then come the instance fields, which HotSpot reorders to pack them tightly, and finally padding so the size is a multiple of 8. For our Order, that's 12 bytes of header, 8 for the long, 4 for the int, 4 for the compressed String reference and 1 for the boolean: 29 bytes, padded to 32. The diagram lays out the bytes.

Fig. 2

A 32-byte bar for one Order object: an 8-byte mark word, a 4-byte klass pointer, the int quantity filling the gap at offset 12, the 8-byte long id, the 4-byte customer reference, the 1-byte boolean and 3 bytes of padding; below it, a bare new Object() takes 16 bytes

A 32-byte bar for one Order object: an 8-byte mark word, a 4-byte klass pointer, the int quantity filling the gap at offset 12, the 8-byte long id, the 4-byte customer reference, the 1-byte boolean and 3 bytes of padding; below it, a bare new Object() takes 16 bytes

Notice that the String reference costs 4 bytes, but the String it points to is a separate object with its own header and its own byte[]. That's why "how big is this object" has a shallow answer (32 bytes) and a retained answer (everything it keeps alive). You can verify layouts yourself with the OpenJDK JOL tool: ClassLayout.parseClass(Order.class).toPrintable().

03Class objects and class loading

Every loaded class has exactly one java.lang.Class instance representing it, per class loader. It's an ordinary heap object, and it's your doorway to the class at run time: its name, its methods, its static fields (HotSpot stores them in this mirror object).

Order a = new Order(1001, "alice");

Class<? extends Order> k1 = a.getClass();          // from an object
Class<Order> k2 = Order.class;                     // class literal, no object needed
Class<?> k3 = Class.forName("com.shop.Order");     // by name, may throw ClassNotFoundException

System.out.println(k1 == k2 && k2 == k3);          // prints: true
System.out.println(k2.getSimpleName());            // prints: Order

A class goes through a fixed lifecycle before you can use it. The diagram below walks through it, including the part interviewers probe most: what actually triggers initialisation.

Fig. 3

Five numbered stages: load the class file, link it (verify, prepare static fields with defaults, resolve symbols), initialise it by running clinit once, use it, and finally unload it when its class loader is collected; below, a list of what triggers initialisation and what does not

Five numbered stages: load the class file, link it (verify, prepare static fields with defaults, resolve symbols), initialise it by running clinit once, use it, and finally unload it when its class loader is collected; below, a list of what triggers initialisation and what does not

Loading is lazy: the JVM reads Order.class when something first needs it. Initialisation, which runs static field initialisers and static {} blocks in a method called <clinit>, happens on the first active use: creating an instance, calling a static method, or reading or writing a static field that isn't a compile-time constant. A subclass initialising triggers its superclass first.

Try it yourself first

Before reading on, decide which of the four lines below prints PricingRules initialised.

class PricingRules {
    static { System.out.println("PricingRules initialised"); }
    static final int MAX_ITEMS = 50;                          // compile-time constant
    static final List<String> REGIONS = List.of("EU", "US");  // not a constant
}

System.out.println(PricingRules.MAX_ITEMS);   // prints: 50 (no init, value inlined by javac)
Class<?> k = PricingRules.class;              // no init
PricingRules[] slots = new PricingRules[4];   // no init, only the array class is created
System.out.println(PricingRules.REGIONS);     // prints: PricingRules initialised, then [EU, US]

Notice that only the last line triggers it. MAX_ITEMS is a primitive static final with a constant initialiser, so javac copies 50 straight into the caller's bytecode and the class never needs to run. A class literal and an array type don't count as active use either. Class.forName("..."), on the other hand, does initialise by default.

04Real world: identity and class loaders in production

Two consequences of the model above show up in real systems, not only in interviews.

First, identity is not equality. Putting Order objects in a HashSet without overriding equals and hashCode means two orders with the same id are two different entries, because the default methods use identity. The default hashCode() is the identity hash code: generated on first call (in HotSpot, from a thread-local random generator, not from the memory address) and then stored in the mark word, so it stays stable even when the GC moves the object.

Second, a class's identity is its name plus the loader that defined it. Load com.shop.Order through two different class loaders and you get two unrelated classes with two different Class objects.

In the real world

A team hot-reloads code in development with Spring Boot DevTools, which uses a separate "restart" class loader for application classes. An object cached in a library loaded by the base class loader survives the restart, and the next request fails with ClassCastException: class com.shop.Order cannot be cast to class com.shop.Order. Same name, different loaders, so different types. The same thing happens in application servers with multiple deployments and in plugin systems. The fix is to make sure one loader owns the class, or to stop caching instances across reloads.

05Advanced: header sizes, identity and the edge cases

A few sizes worth knowing for a 64-bit HotSpot JVM with default flags (compressed oops and compressed class pointers, heaps under ~32 GB):

What Size Why
new Object() 16 bytes 12-byte header plus 4 bytes padding
Object with one int 16 bytes the int fits in the padding
Our Order 32 bytes 29 bytes of data, rounded up to 8
new int[0] 16 bytes header plus a 4-byte length field
Reference field 4 bytes compressed oop; 8 bytes with heaps of 32 GB or more
Performance angle

Headers dominate small objects. A million Order objects is about 32 MB shallow, and 12 MB of that is headers. That's the motivation for Compact Object Headers, which shrink the header to 8 bytes: experimental in Java 24 (JEP 450) and a product feature in Java 25 (JEP 519) behind -XX:+UseCompactObjectHeaders. On Java 21, the practical levers are fewer objects (primitive arrays instead of lists of wrappers) and staying under the 32 GB compressed-oops limit.

Where candidates slip

Many candidates say "objects live on the heap, so new always allocates on the heap". That's the language-level truth, but the JIT may scalar-replace an object that never escapes a method (escape analysis), keeping its fields in registers with no allocation at all. Say "semantically on the heap; the JIT can eliminate allocations that don't escape". It shows you know the difference between the model and the implementation.

Some other edge cases interviewers like:

  • A class is not an object, but it is represented by one. Order.class is a Class<Order> object; the metadata itself (bytecode, vtable, constant pool) lives in Metaspace, native memory outside the heap since Java 8.
  • getClass() returns the runtime class. For Object o = new Order(...), o.getClass() is Order.class, not Object.class. The static type decides what compiles; the runtime class decides which override runs.
  • Objects die by reachability, not scope. When a goes out of scope, the object is eligible for GC only if nothing else (like b, or a cache) still references it.
  • Initialisation runs once and is thread-safe. The JVM holds an initialisation lock per class, so two threads racing to first use see <clinit> run exactly once. If it throws, you get ExceptionInInitializerError the first time and NoClassDefFoundError on every later use.

06How to say it in the interview

When I ask "what's the difference between a class and an object?", I'm listening for three things beyond the blueprint line: that variables hold references, so == is identity; that an object has a header and a real size you can estimate; and that classes are loaded lazily and initialised on first active use. Mentioning that class identity includes the class loader is a strong senior signal.

In the interview

"A class is a type: it declares fields and methods and is loaded once per class loader, with its metadata in Metaspace and one java.lang.Class object representing it. An object is an instance allocated on the heap with new. In HotSpot each object has a 12-byte header by default, an 8-byte mark word for hash, GC age and locking and a 4-byte compressed class pointer, then its fields, padded to a multiple of 8, so an empty object is 16 bytes. Variables hold references, so == compares identity, and equals is what compares contents. Classes are initialised lazily on first active use: new, a static method call or a non-constant static field access, and reading a compile-time constant or writing Order.class doesn't trigger it. And class identity is the name plus the loader, which is why you can see Order cannot be cast to Order across class loaders."

On the whiteboard

class  = type + code, loaded once per loader (Metaspace) + 1 Class object (heap)
object = header + fields + padding (heap), created by new
  header: mark word 8B (hash, GC age, lock) + klass ptr 4B
  new Object() = 16B; Order{long,int,boolean,ref} = 32B
variable = reference; b = a copies the reference
== on refs  -> identity;  equals -> contents (if overridden)
new -> new / dup / invokespecial <init>
lifecycle: load -> link (verify, prepare, resolve) -> <clinit> -> use -> unload
init on: new, static call, non-constant static field, subclass init, forName
not on:  Order.class, new Order[n], static final constants
class identity = name + defining class loader
Practise this topicClasses & objects: interview questions & scenarios
Further reading (7)