Wake Me Up When The Interview Ends: The Agony of Java Backend's Memorization Game

The Price of Admission: Why We Must Master the Java Backend's Standardized Answers😥

seal


Basic

Differences in Usage between == and equals

First of all, == is an operator, and equals is a method.

==

Compare the values of variables to see if they are same.

  1. If the objects being compared are of Basic data type, the comparison is done to see if the values are the same.
  2. If the objects are of Reference data type, the comparison is done by address.

equals

Compare the values of objects to see if they are same.

Equals method exists in the Object class:

public boolean equals(Object obj) {
    return (this == obj);
}

So when this method isn't overridden, it "equals" to the == in fact. But you can override this method to change it's logic.


String

Differences between String, StringBuffer and StringBuilder

Items String StringBuffer StringBuilder
Mutability Immutable. The core of the String class is a char[] array named value, which is modified with the final keyword (the array reference cannot be changed) and has no external modification entry (no setter or expansion method). Each operation (e.g., +, substring) generates a new String object. Mutable. StringBuffer inherits from AbstractStringBuilder (the parent class is not modified with final), so it can directly modify the internal char[] array without creating new objects. Same as StringBuffer (inherits from AbstractStringBuilder and supports direct modification of the internal char[] array, no new objects created).
Thread Safety Thread-safe. Since the internal value array cannot be modified, concurrent access will not cause data confusion. Thread-safe. All modification methods (e.g., append(), insert()) are modified with the synchronized keyword, ensuring only one thread can execute the method at a time. See Note 1 for details. Not thread-safe. It has the same mutable features as StringBuffer but lacks synchronized modification—concurrent modification may cause data errors (e.g., index out of bounds, character overwriting). See Note 2 for details.

📕Note 1
public synchronized StringBuffer append(String str)
public synchronized StringBuffer insert(int offset, String str)
public synchronized StringBuffer delete(int start, int end)
you can see that synchronized is used extensively here, it is a kind of Pessimistic locking, Unfair locking, Reentrant locking and Exclusive locking.
This ensure Thread safety.

📕Note 2
Faster because no locks are used


The Number and Definition of String Variables

There are three ways to create:

  1. String constant (with double quotes)
  2. String splicing
  3. new String

For example,


String a = "aaa";
String b = "bbb" + a + new String("ccc");

This code will create at least 6 objects in memory.

  1. Object 1: The String Literal "aaa"

    This literal is created and placed in the String Pool (which resides in the Heap in modern Java versions).

    The reference a points to this object in the pool.

  2. Object 2: The String Literal "bbb"

    It is created and placed in the String Pool.

  3. Object 3: The String Literal "ccc"

    It is created and placed in the String Pool.

  4. Object 4: The Heap-allocated String Object

    The new keyword in new String("ccc") forces the creation of a new String object on the Heap, separate from the literal in the pool.

  5. Object 5: The Temporary StringBuilder Object

    Since the concatenation involves a variable (a), the Java compiler typically optimizes this operation by internally using a StringBuilder instance for efficient appending.

    This temporary StringBuilder object is created on the Heap.

  6. Object 6: The Final Resulting String Object

    The StringBuilder's toString() method is called to finalize the operation, which results in a new String object (containing "aaabbbccc") being created on the Heap.

    The reference b points to this final result object.

Key Terminology:
String Pool: 字符串常量池
Heap: 堆 (内存)
String Literal: 字符串字面量
Force the creation: 强制创建
Concatenation/Appending: 拼接/连接
Reference: 引用
Optimizer/Compiler Optimization: 编译器优化


String Pool , Perm , Heap and Meta

Method Area

The method area is shared by all JVM threads.
It is mainly used to store class information, constant pool, method data, method code, etc.
The method area is logically part of the heap, but in order to distinguish with the heap, so it is usually named with non-heap

PermGen Space

PermGen Space is used as a method area, all such as class information, string pool are store in here.

The PermGen is part of the JVM memory, and its maximum size is determined when the JVM is initialized.

Defeats:

  1. The Permanent Generation (PermGen) is a section of JVM memory used to store class metadata (such as class information, the constant pool, and method data).

  2. PermGen has a fixed size, which is set by the parameters -XX:PermSize and -XX:MaxPermSize.

  3. Due to its fixed size, the JVM throws a java.lang.OutOfMemoryError: PermGen space error when the PermGen space runs out.

  4. PermGen space cannot expand automatically, making it prone to memory overflow when a large number of classes are loaded or many dynamically generated classes are used.

Meta Space

The Metaspace introduced in Java 8 replaced the Permanent Generation (PermGen). The main changes include:

  1. Dynamic Sizing: The size of the Metaspace is no longer fixed; it can expand dynamically, which solves the problem of insufficient PermGen space.

  2. Native Memory Usage: Unlike PermGen, Metaspace is not located in the Java Heap. Instead, it utilizes native memory (or system memory), reducing the dependency on the heap.

  3. GC Management: Garbage Collection for the Metaspace is managed by the JVM, but it is separate from the garbage collection of the heap memory, allowing for independent processing.

So after java 8, method area are changed to meta space. PermGen space was deleted.

Key Terminology:
String Pool: 字符串常量池
Perm(PermGen space): 永久代
Heap: 堆
Meta(Metaspace): 元空间


JVM Memory Generations: Young, Old, and Permanent

The Java Heap memory is fundamentally divided into generations to optimize garbage collection (GC) and overall performance.

Young Generation (YoungGen)

The Young Generation is the primary allocation area for new objects.

  • It involves the Eden Space and two Survivor Spaces (S0 and S1).

  • New objects are initially created in the Eden Space.

  • If an object survives multiple Minor GC cycles (typically 15 times in many JVMs), it is promoted to the Old Generation.

Old Generation (OldGen)

The Old Generation is designed for long-lived objects that have survived repeated Minor GC cycles.

  • It holds objects that are considered fixed resources for the application's runtime.

  • It undergoes less frequent but more complex GC operations (Major GC / Full GC), which is more time-consuming.

Permanent Generation (PermGen) / Metaspace

  • PermGen (Pre-Java 8): Used to store class metadata and static variables.

  • Metaspace (Java 8+): Replaced PermGen. Stores class metadata in Native Memory, reducing the risk of OutOfMemoryError compared to the fixed-size PermGen.

String Interning and the String Pool

String Pool

Java provides the String Pool to ensure faster String comparison and reduced memory usage by sharing immutable String objects. It functions as a system-provided cache.

There are two methods to add a String object to the String Pool:

  1. String Literals: Storing a String object directly by using double quotes (e.g., "hello").

  2. Explicit Interning: Using the intern() method to add a String object to the String Pool at runtime.

The intern() Method

public native String intern();

The intern() method performs the following check:

  • It uses the equals() method to judge whether a String object with the same content already exists in the String Pool.

  • If the answer is false, the method will add the object (or its reference, depending on the JVM version) to the String Pool.

  • Regardless of whether it was already in the pool or added, this method will return the reference to the pooled String object.

Mechanism and Evolution

Location JVM Version Storage Behavior
PermGen Pre-Java 7 Stored a copy of the String object itself in the PermGen area.
Heap Java 7 and newer Stored a reference to the String object (which remains in the main Heap memory).

Purpose

String creation can quickly become a memory bottleneck, especially in systems dealing with high data volumes.

In some extreme scenarios, our code may end up generating a vast quantity of String objects. Even if these individual strings are slim in size, their sheer volume can lead to significant memory consumption.

Goal: The primary objective of using the String Pool and the intern() method is to prevent the redundant creation (or repeated use of the 'new' operator) of identical strings, thus conserving memory and improving comparison speed.


Static

Five Ways to use Static keyword


1.Modify member properties


class Test{
    public static String name = "seal";
}

public class demo{
    public static void main(String[] args){
        System.out.println(Test.name);
    }
}


2.Modify member methods

It eliminates the overhead and resource consumption associated with explicit object instantiation (using new).


class Test{
    public static void hello(String name){
        System.out.println("hello" + name);
    }
}

class demo{
    public static void main(String[] args){
        Test.hello("seal");
    }
}


3.Modify code blocks

The static {} is the static block, which executes only once when the class is loaded by the class loader, typically for initializing static variables. The instance initializer {} (without static) executes every time an object is created, helping reuse initialization code across all constructors.


class Test{

    static {
        System.out.println("static blocks");
    }

    {
        System.out.println("none ststic blocks");
    }

    public Test(){
        System.out.println("none prarm build");
    }

    public Test(String name){
        System.out.println("build with prarm: " + name);
    }

}


4.Modify inner classes

In some contexts, we can reference and use a specific static nested class directly, eliminating the need to instantiate its enclosing (outer) class.

// Inner static class_Singleton pattern
public class Singleton{
    private static class SingletonHandler{
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton(){

    }

    public static final Singleton getSingleton(){
        return SingletonHandler.INSTANCE;
    }
}


5.Static import

The use of the static keyword during import, combined with the ".*" at the end of the imported class name, serves to directly import all static methods from the PrintHelper class.
Unlike non-static imports, a static import allows you to call these static methods without needing to prefix them with the class name (i.e., you don't need ClassName.methodName).
Provided there are no naming conflicts with methods in the current class, you can directly invoke them using just the "methodName," making them feel like they are native methods of your own class.


package com.seal.utils;

public class printHelper{
    public static void print(Object o){
        System.out.println(o);
    }
} 

package com.seal.main;

import static com.seal.utils.printHelper.*;

public class demo{
    public static void main(String[] args){
        print("seal");
    }

}


Java Class Loading Process

Process Overview

Loading => Linking (Verification + Preparation + Resolution) => Initialization => Usage => Unloading

1. Loading

(The process of transforming the Java binary file, or .class file, from the hard disk into a Class object in memory.)

  • Acquisition: Obtain the binary byte stream that defines the class using its fully qualified name.
  • Transformation: Convert the static storage structure represented by this byte stream into the runtime data structure within the Method Area.
  • Instantiation: Create a java.lang.Class object in memory (not necessarily the Heap; HotSpot uses the Method Area) that represents this class, serving as the access point to all data related to the class in the Method Area.

2. Linking

(The process of replacing symbolic references with direct references and allocating memory for static variables.)

  • Verification: Checks the correctness and compliance of the loaded class file data with the Java Virtual Machine Specification.
  • Preparation: Allocates memory for class variables (static variables) in the Method Area and sets them to their zero values (0, false, null, etc.).
    • Exception: For static variables declared as static final (String or primitive types), they are directly assigned their final value during this stage. Example: static final int a = 12; will assign the value 12 during Preparation.
  • Resolution (Optional): Replaces symbolic references in the Constant Pool with direct references.
    • Symbolic Reference: An indirect reference defined by a set of symbols, independent of the VM's memory layout. The referenced target may not yet be loaded.
    • Direct Reference: A reference that is directly tied to the VM's memory layout (e.g., a pointer). If a direct reference exists, the target must already be present in memory.

3. Initialization

(The active process of executing the class initializer method, <clinit>().)

  • Execution: Executes the assignment actions for class variables (static variables) and runs the static statement blocks (executed strictly in the order they appear in the source code, from top to bottom).
  • Priority: Initialization follows a hierarchy: Static members and blocks are executed first, then the Parent class is initialized before the Child class.
  • Key Point: Initialization operates on class variables (static variables), not object instance variables.

4. Usage

(Taking object instantiation with new as an example.)

If this is the first time the Dog object is being created (i.e., the class to which the object belongs has not yet been loaded into memory), the preceding Initialization steps are executed first.

  • Allocation: Allocate space for the Dog object (including instance variables) on the Heap. Attributes are set to default values (0 for numbers, null for references/characters, false for booleans).
  • Instance Initialization: Assign values to instance variables and execute instance initialization blocks (if any).
  • Constructor Execution:
    • Check for a parent class; if one exists, the parent's constructor is called first.
    • Execute the current class's constructor.

5. Unloading

(The class is eventually removed from the Method Area, typically when its ClassLoader is garbage collected, which is rare for application classes.)