Java Interview Opps, Collection framework, GC, Comparator - Yash-777/MyWorld GitHub Wiki

/** Number of CPUS, to place bounds on some sizings */
static final int NCPU = Runtime.getRuntime().availableProcessors();

ClassNotFoundException and NoClassDefFoundError both indicate a missing class at runtime, but they occur in different scenarios and have different root causes and handling requirements.

  • ClassNotFoundException happens when your code explicitly tries to load a class by name and fails because it is missing at runtime.
  • NoClassDefFoundError happens when your code compiled successfully because the class was present, but the JVM cannot find the class's definition during execution.

if-else vs switch, Ternary Operator
πŸ”Ή if - else
πŸ”Ή Ternary Operator
πŸ”Ή switch
πŸ”Ή if - else
 * Evaluates boolean expression β€” any condition
 * Handles ranges, complex conditions, multiple types
 * Goes top to bottom β€” first match wins
 * else is optional
int marks = 75;

// multiple condition expressions
if (marks >= 90) {
System.out.println("Grade A");
} else if (marks >= 75) {
System.out.println("Grade B");
} else if (marks >= 60) {
System.out.println("Grade C");
} else {
System.out.println("Fail");
}

// complex conditions β€” switch cannot do this βœ…
if (age > 18 && country.equals("India") && !isBanned) {
System.out.println("Eligible");
}

// null check β€” switch throws NPE on null βœ…
if (name != null && name.equals("John")) { }

// range check β€” switch cannot do this βœ…
if (salary >= 50000 && salary <= 100000) { }
πŸ”Ή One-line Conditional (Ternary Operator)

// condition ? true : false
int age = 20;
String result = age >= 18 ? "Adult" : "Minor";   // βœ… one line

// nested ternary β€” avoid, hard to read ⚠️
String grade = marks >= 90 ? "A" : marks >= 75 ? "B" : marks >= 60 ? "C" : "Fail";

// common uses
int max    = a > b ? a : b;
String msg = user != null ? user.getName() : "Guest";
return isValid ? process() : throwError();
πŸ”Ή switch
 * Evaluates single variable against constants
 * Cleaner than multiple if-else for fixed values
 * Works with int, char, String, enum β€” not boolean, long, float
 * Fall-through β€” without break continues to next case

// Traditional switch β€” fall-through risk ⚠️
int day = 3;
switch (day) {
    case 1:
        System.out.println("Monday");
        break;          // ⚠️ forget break β€” falls to next case
    case 2:
        System.out.println("Tuesday");
        break;
    default:
        System.out.println("Other");
}

// switch with enum β€” Best Use Case βœ…
// String constants -> String status = "ACTIVE";
enum Status { PENDING, ACTIVE, INACTIVE, BLOCKED }
Status status = Status.ACTIVE;
switch (status) {
    case PENDING:
        System.out.println("Awaiting approval");
        break;
    case ACTIVE:
        System.out.println("User is active");
        break;
    case INACTIVE:
        System.out.println("User inactive");
        break;
    case BLOCKED:
        System.out.println("User blocked");
        break;
}

Encapsulation is the process of binding (wrapping) data members and member functions into a single unit.

Encapsulation @SeleniumExpress

πŸ”’Encapsulation πŸ’Š Diagram πŸ‘‡
Encapsulation is the process of binding (wrapping) data members and member functions into a single unit.

Example: A class in Object-Oriented Programming (OOP).
 * Data members should not be directly accessible from outside the class.
 * They are usually declared as private to ❌ restrict direct access.
 * Access to these data members is provided through public methods such as getter and setter methods (or other controlled methods).

Simple Explanation: Encapsulation helps in data hiding and protects data from unauthorized access by allowing controlled interaction through methods.
πŸ” Concept in One Line: Encapsulation = Data + Methods packed together like medicine inside a capsule with controlled access.

Class (The Container): `Data Members (Variables)` & `Member Functions (Methods)` πŸ‘‰ Both are packed into one single unit.
πŸ‘‰ Class   = Capsule πŸ’Š
πŸ‘‰ Data    = Medicine inside
πŸ‘‰ Methods = Safe way to use it
image
Polymorphism means "one name, many forms." (Method overloading, Method overriding)

The ability of a reference variable to change its behavior according to the object instance it is holding. The overridden methods behave differently based on the actual object being referenced. Polymorphism allows you to define one interface and have multiple implementations.

There are two types of Polymorphism in Java:

  • Compile-time polymorphism β†’ Method overloading (decided at compile time).
  • Runtime polymorphism β†’ Method overriding (decided at runtime).
Compile-time Polymorphism (Static binding)
Method overloading
Runtime Polymorphism (Dynamic binding)
Method overriding
 * Same class, same method name, different parameters (type/count/order)
 * Resolved at compile time (Static binding)
 * Return type can be different but alone cannot differentiate overloading
 * Access modifier can be anything β€” no restriction
 * null β†’ compiler picks most specific type; if tie β†’ compile error
 * @Override annotation not applicable
 * Can be done in same class or child class (child overloading parent method)
 * final, static, private methods all can be overloaded βœ…
 * Parent β†’ Child, same method name + same parameters + same return type
 * Resolved at runtime based on object type (Dynamic binding)
 * Animal ref = new Dog() β†’ ref.instanceSound() calls Dog's method
 * @Override annotation recommended β€” catches mistakes at compile time
 * Return type can be covariant (child can narrow return type β€” Animal β†’ Dog) βœ…
 * Access modifier can only be widened, never narrowed
      private β†’ default β†’ protected β†’ public
              (widening direction β†’)
 * private methods cannot be overridden β€” they are invisible to child class
 * final methods cannot be overridden ❌
 * Static Hiding (Parent β†’ Child)
     static methods cannot be overridden β€” they get hidden instead
 * constructor cannot be overridden
 * Overriding method cannot throw broader checked exceptions than parent
    * Parent throws IOException β†’ Child can throw IOException or narrower βœ…
    * Parent throws IOException β†’ Child throws Exception ❌
 * abstract methods must be overridden in concrete child class
/**
 * Comprehensive Polymorphism Demo
 * Covers:
 * - Static method hiding vs Instance method overriding
 * - Static method call via null object
 * - Method overloading with null (String vs Object)
 * - Ambiguous overloading (Integer vs Long)
 * - Covariant return type in overriding
 * - Access modifier rules in override/overload
 */
// ======================== PARENT CLASS ========================
class Animal {

    // 1. STATIC METHOD β€” will be HIDDEN (not overridden) in child
    static void staticSound() {
        System.out.println("Animal: static sound (Animal class)");
    }

    // 2. INSTANCE METHOD β€” will be OVERRIDDEN in child
    void instanceSound() {
        System.out.println("Animal: instance sound");
    }

    // 3. OVERLOADING: null call β€” Object vs String
    void greet(Object o) {
        System.out.println("greet(Object) called");
    }
    void greet(String s) {
        System.out.println("greet(String) called");
    }

    // 4. OVERLOADING: null call β€” Integer vs Long (AMBIGUOUS)
    void calculate(Integer i) {
        System.out.println("calculate(Integer) called");
    }
    void calculate(Long l) {
        System.out.println("calculate(Long) called");
    }

    // 5. COVARIANT RETURN TYPE β€” parent returns Animal
    Animal getInstance() {
        System.out.println("Animal getInstance()");
        return new Animal();
    }

    // 6. ACCESS MODIFIER β€” protected in parent (child can widen it to public)
    protected void accessDemo() {
        System.out.println("Animal: protected accessDemo()");
    }
}
// ======================== CHILD CLASS ========================
class Dog extends Animal {

    // 1a. STATIC METHOD HIDING β€” not overriding, just hiding
    //     Resolved at COMPILE TIME based on reference type
    static void staticSound() {
        System.out.println("Dog: static sound (Dog class)");
    }

    // 1b. INSTANCE METHOD OVERRIDING β€” resolved at RUNTIME based on object type
    @Override
    void instanceSound() {
        System.out.println("Dog: instance sound (overridden)");
    }

    // 5. COVARIANT RETURN TYPE β€” child can return Dog (subtype of Animal)
    //    Valid override β€” return type is narrowed down
    @Override
    Dog getInstance() {
        System.out.println("Dog getInstance() β€” covariant return");
        return new Dog();
    }

    // 6. ACCESS MODIFIER β€” widening protected β†’ public is ALLOWED in override
    //    Narrowing (public β†’ protected) is NOT allowed β€” compile error
    @Override
    public void accessDemo() {
        System.out.println("Dog: public accessDemo() β€” widened access modifier");
    }
}
// ======================== MAIN CLASS ========================
public class PolymorphismDemo {

    public static void main(String[] args) {
        System.out.println("========== 1. Static Hiding vs Instance Overriding ==========");
        Animal ref = new Dog(); // Animal reference, Dog object

        // Static method β€” resolved at COMPILE TIME β†’ Animal's method called (HIDING)
        ref.staticSound();          // Output: Animal: static sound
        Animal.staticSound();       // Output: Animal: static sound
        Dog.staticSound();          // Output: Dog: static sound

        // Instance method β€” resolved at RUNTIME β†’ Dog's method called (OVERRIDING)
        ref.instanceSound();        // Output: Dog: instance sound

        System.out.println("\n========== 2. Static Method Call via NULL Object ==========");
        Animal nullAnimal = null;
        // No NullPointerException β€” static methods belong to CLASS not object
        // Compiler replaces nullAnimal.staticSound() β†’ Animal.staticSound()
        nullAnimal.staticSound();   // Output: Animal: static sound βœ…

        System.out.println("\n========== 3. Overloading with NULL β€” Object vs String ==========");
        Animal a = new Animal();
        // String is more specific than Object β†’ compiler picks greet(String)
        a.greet(null);              // Output: greet(String) called βœ…
        a.greet((Object) null);     // Output: greet(Object) called β€” explicit cast

        System.out.println("\n========== 4. Overloading NULL β€” Integer vs Long (AMBIGUOUS) ==========");
        // a.calculate(null); ❌ COMPILE ERROR
        // Both calculate(Integer) and calculate(Long) match null
        // Neither Integer nor Long is more specific than the other
        // Fix: explicit cast
        a.calculate((Integer) null); // Output: calculate(Integer) called βœ…
        a.calculate((Long) null);    // Output: calculate(Long) called βœ…
        System.out.println("Note: a.calculate(null) β†’ COMPILE ERROR (ambiguous method call)");

        System.out.println("\n========== 5. Covariant Return Type ==========");
        // Parent reference β€” getInstance() returns Animal type at compile time
        Animal animalObj = ref.getInstance();   // Runs Dog's getInstance() at runtime
        // Dog reference β€” can directly get Dog type
        Dog dog = new Dog();
        Dog dogObj = dog.getInstance();         // Dog's covariant return βœ…

        System.out.println("\n========== 6. Access Modifier in Override ==========");
        // Dog widened accessDemo() from protected β†’ public
        // Calling via Animal reference still works βœ…
        ref.accessDemo();           // Output: Dog: public accessDemo()

        System.out.println("\n========== Summary of Rules ==========");
        System.out.println("Static method    β†’ HIDDEN   (compile-time, reference type decides)");
        System.out.println("Instance method  β†’ OVERRIDDEN (runtime, object type decides)");
        System.out.println("null + static    β†’ No NPE, resolved by compiler to class");
        System.out.println("null + overload  β†’ Most specific type wins; ambiguous = compile error");
        System.out.println("Covariant return β†’ Child can narrow return type (Dog extends Animal)");
        System.out.println("Access modifier  β†’ Override can WIDEN (protectedβ†’public) βœ…");
        System.out.println("                   Override can NOT NARROW (publicβ†’protected) ❌");
        System.out.println("Overload         β†’ Access modifier can be anything, no restriction βœ…");
    }
}
========== 1. Static Hiding vs Instance Overriding ==========
Animal: static sound (Animal class)
Animal: static sound (Animal class)
Dog: static sound (Dog class)
Dog: instance sound (overridden)
========== 2. Static Method Call via NULL Object ==========
Animal: static sound (Animal class)
========== 3. Overloading with NULL β€” Object vs String ==========
greet(String) called
greet(Object) called
========== 4. Overloading NULL β€” Integer vs Long (AMBIGUOUS) ==========
calculate(Integer) called
calculate(Long) called
Note: a.calculate(null) β†’ COMPILE ERROR (ambiguous method call)
========== 5. Covariant Return Type ==========
Dog getInstance() β€” covariant return
Dog getInstance() β€” covariant return
========== 6. Access Modifier in Override ==========
Dog: public accessDemo() β€” widened access modifier
========== Summary of Rules ==========
Static method    β†’ HIDDEN   (compile-time, reference type decides)
Instance method  β†’ OVERRIDDEN (runtime, object type decides)
null + static    β†’ No NPE, resolved by compiler to class
null + overload  β†’ Most specific type wins; ambiguous = compile error
Covariant return β†’ Child can narrow return type (Dog extends Animal)
Access modifier  β†’ Override can WIDEN (protectedβ†’public) βœ…
Override can NOT NARROW (publicβ†’protected) ❌
Overload         β†’ Access modifier can be anything, no restriction βœ…

java.lang.Object β†’ equals() and hashCode() β€” Why Both Must Be Overridden?



The Contract: If two objects are equal (equals() returns true),
they must have the same hashCode.
What happens if only equals() is overridden?
class Student {
    String name;
    int age;

    @Override
    public boolean equals(Object o) {
        Student s = (Student) o;
        return this.name.equals(s.name) && this.age == s.age;
    }
    // hashCode NOT overridden ❌
}
Fix β€” override both:
@Override
public int hashCode() {
    return Objects.hash(name, age); // Java 7+
}
HashMap / HashSet first use hashCode() to choose the bucket.
Then they use equals() to compare objects inside that bucket.
If two objects have different hashCode() values (default is memory address), they go into different buckets. In that case, equals() is never called.
Student s1 = new Student("Alice", 20);
Student s2 = new Student("Alice", 20);

s1.equals(s2); // βœ… true β€” your custom equals works

Set set = new HashSet<>();
set.add(s1);
set.contains(s2); // ❌ FALSE! Should be true but isn't

Map<Student, String> map = new HashMap<>();
map.put(s1, "Engineer");
map.get(s2); // ❌ returns null! s2 can't find s1's bucket
    Easy rule to remember:
  • hashCode() β†’ finds the bucket (neighbourhood)
  • equals() β†’ finds the exact object (house)
    If the bucket is wrong, the object won’t be found.

🧠 Java Memory Management Structure Overview



When a Java program runs, JVM divides memory into:

  • Heap β€” objects and instance variables live here; shared across threads, Managed by: Garbage Collector
    • Error: OutOfMemoryError, Size: Large, Speed: Slower
    • πŸ“Œ Stores: Objects, Instance variables, Arrays
    • Divided into Young Generation (Eden + S0 + S1) and Old Generation
    Person objRef = new Person();
    // objRef       β†’ Stack (reference)
    // new Person() β†’ Heap  (object)
  • Stack β€” method calls, local variables; one per thread; Managed by: JVM auto (LIFO)
    • Error: StackOverflowError if too deep recursion, Size: Small, Speed: Fast
    • πŸ“Œ Stores: Local variables, Method calls, Primitive values, Object references (not objects)
    public void test() {
        int x = 10;              // Stack (primitive)
        Person p = new Person(); // p β†’ Stack (ref), new Person() β†’ Heap
    }
  • Method Area (Metaspace) β€” shared across threads; Managed by: JVM / Full GC
    • Error: OutOfMemoryError: Metaspace, Size: Dynamic, Speed: Slower
    • πŸ“Œ Stores: Class metadata, Static variables, Method information, Constants, Annotations
    • Before Java 8 β†’ PermGen (fixed size, inside JVM)
    • From Java 8+ β†’ Metaspace (dynamic size, uses native memory)
    public class Person {
        static int count = 0; // Metaspace (static)
        String name;          // Heap (instance variable)
    }
  • PC Register β€” tracks current instruction per thread; one per thread
    • Size: Very small, auto updated after each instruction
    • For native methods β†’ value is undefined
  • Native Method Stack β€” for native (C/C++) method calls via JNI; one per thread
    • Error: StackOverflowError or OutOfMemoryError
    System.arraycopy(src, 0, dest, 0, length); // internally calls C level code
Area Stores GC Applies?
Eden Newly created objects βœ… Minor GC
Survivor S0/S1 Objects surviving Minor GC βœ… Minor GC
Old Generation Long lived / large objects βœ… Major GC
Metaspace Class metadata, static vars βœ… Full GC
Stack Method calls, local variables ❌ Auto cleared
Code Cache JIT compiled code ❌ Managed by JIT
Native Memory OS level resources ❌ Manual / OS
🧠 JVM Heap Structure 🎯 Object LifecycleπŸ”₯ flow
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                        JVM MEMORY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
          new Object()
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ HEAP MEMORY ──────┐
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”‚
β”‚ β”‚         YOUNG GENERATION            β”‚                 β”‚
β”‚ β”‚                                     β”‚                 β”‚
β”‚ β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”   β”‚                 β”‚
β”‚ β”‚  β”‚     EDEN     β”‚  β”‚ S0 β”‚  β”‚ S1 β”‚   β”‚                 β”‚
β”‚ β”‚  β”‚              β”‚  β”‚    β”‚  β”‚    β”‚   β”‚                 β”‚
β”‚ β”‚  β”‚  new Object()β”‚  β”‚age β”‚  β”‚age β”‚   β”‚                 β”‚
β”‚ β”‚  β”‚  allocated   β”‚  β”‚ 1  β”‚  β”‚ 2  β”‚   β”‚                 β”‚
β”‚ β”‚  β”‚    here      β”‚  β”‚    β”‚  β”‚    β”‚   β”‚                 β”‚
β”‚ β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”¬β”€β”€β”˜  β””β”€β”¬β”€β”€β”˜   β”‚                 β”‚
β”‚ β”‚         β”‚    Minor GCβ”‚       β”‚      β”‚                 β”‚
β”‚ β”‚         β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”˜       β”‚      β”‚                 β”‚
β”‚ β”‚         β–Ό    β–Ό               β”‚      β”‚                 β”‚
β”‚ β”‚       Eden full?             β”‚      β”‚                 β”‚
β”‚ β”‚       Minor GC runs  β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚                 β”‚
β”‚ β”‚       survivors move S0 ↔ S1        β”‚                 β”‚
β”‚ β”‚       age counter +1 each cycle     β”‚                 β”‚
β”‚ └──────────────────┬───────────────── β”˜                 β”‚
β”‚                    β”‚ age >= threshold (default 15)      β”‚
β”‚                    β–Ό                                    β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚ β”‚           OLD GENERATION              β”‚               β”‚
β”‚ β”‚                                       β”‚               β”‚
β”‚ β”‚   Long lived objects promoted here    β”‚               β”‚
β”‚ β”‚   Large objects directly here         β”‚               β”‚
β”‚ β”‚                                       β”‚               β”‚
β”‚ β”‚   Major GC runs when Old Gen fills up β”‚               β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ NON-HEAP MEMORY ─────┐
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚                   METASPACE                         β”‚ β”‚
β”‚ β”‚   (replaced PermGen from Java 8+)                   β”‚ β”‚
β”‚ β”‚   β€’ Class metadata      β€’ Method info               β”‚ β”‚
β”‚ β”‚   β€’ Static variables    β€’ Constants                 β”‚ β”‚
β”‚ β”‚   β€’ Class loaders       β€’ Annotations               β”‚ β”‚
β”‚ β”‚                                                     β”‚ β”‚
β”‚ β”‚   grows dynamically β€” uses native memory            β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                         β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚  CODE CACHE  β”‚   β”‚    STACK     β”‚   β”‚  NATIVE MEMORYβ”‚ β”‚
β”‚ β”‚              β”‚   β”‚              β”‚   β”‚               β”‚ β”‚
β”‚ β”‚ JIT compiled β”‚   β”‚ method calls β”‚   β”‚ direct buffer β”‚ β”‚
β”‚ β”‚ bytecode     β”‚   β”‚ local vars   β”‚   β”‚ OS resources  β”‚ β”‚
β”‚ β”‚              β”‚   β”‚ stack frames β”‚   β”‚               β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
══════════════════════════════════════════════
Object Age β€” Survivor Threshold
Minor GC 1  β†’  Object moves Eden  β†’ S0  (age = 1)
Minor GC 2  β†’  Object moves S0    β†’ S1  (age = 2)
Minor GC 3  β†’  Object moves S1    β†’ S0  (age = 3)
Minor GC 4  β†’  Object moves S0    β†’ S1  (age = 4)
...
Minor GC 15 β†’  age = 15 β†’ PROMOTED to Old Generation
══════════════════════════════════════════════
                    JVM HEAP
══════════════════════════════════════════════
        🟒 YOUNG GENERATION
        ─────────────────────────
         new Object()
               β”‚
               β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚    Eden     β”‚  ← Eden full? = NO β†’ stays in Eden
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚  (Eden full β†’ Minor GC)
			   β”œβ”€β”€β†’ Dead objects ─────────→ β™» GARBAGE COLLECTED
               β–Ό
		β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Survivor S0  β”‚ age = 1
        β”‚ Survivor S1  β”‚ age = 2, 3, 4 ... (S0 ↔ S1 each Minor GC)
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚  (Age β‰₯ Threshold, e.g., 15)
               β–Ό
══════════════════════════════════════════════
        πŸ”΅ OLD GENERATION (Tenured)
──────────────────────────────────────────────
        Long-living objects promoted here
               β”œβ”€β”€β†’ Dead object────────────→ β™» GARBAGE COLLECTED
               β”‚ (Old Gen fills β†’ Major GC β€” slow, infrequent)
               β–Ό
        Mark β†’ Sweep β†’ Compact
		Still not enough memory?
               β”‚
               β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚           FULL GC                       β”‚
  β”‚  Entire Heap + Metaspace cleaned        β”‚
  β”‚  Triggered by JVM or System.gc()        β”‚
  β”‚  Most expensive β€” Stop The World (STW)  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”œβ”€β”€β†’ Dead objects  ──────────────────────→ β™» GARBAGE COLLECTED
               β”‚
               β–Ό
          Still no memory?
               β”‚
               β–Ό
          πŸ’₯ OutOfMemoryError: Java heap space
══════════════════════════════════════════════
Garbage Collected / Removed
GC Type Area Cleaned Trigger Speed Stop The World
Minor GC Young Gen only (Eden + Survivors) Eden space full βœ… Fast Brief pause
Major GC Old Generation only Old Gen fills up 🐒 Slow Longer pause
Full GC Entire Heap + Metaspace JVM decision / System.gc() 🐒🐒 Slowest Long pause

Java-5 πŸ‘‰ Variable Arguments (Varargs) main(String[] args) vs main(String... args)



Varargs (...) allow a method to accept zero or more arguments of the same type without explicitly creating an array.
Main Purpose πŸ‘‰ To allow flexible number of parameters

  • Only ONE varargs per method allowed, and MUST be LAST parameter
    static void printInfo(String name, int... scores) {
        System.out.println("Name: " + name);
        for (int s : scores) System.out.println("Score: " + s);
    }
    // ❌ varargs not last
    static void wrong(int... nums, String name) { }  // Compile Error
    // ❌ two varargs
    static void wrong(int... a, int... b) { }        // Compile Error
  • Varargs are internally treated as an array.
    String... args β†’ compiled to β†’ String[] args
Without Varargs β€” old way (overloading needed) parameter's of sametype With Varargs β€” clean single method
static int add(int a, int b)           { return a + b; }
static int add(int a, int b, int c)    { return a + b + c; }  // duplicate!
static int add(int a, int b, int c, int d) { return a + b + c + d; } // more duplicate!
static int add(int... numbers) {        // int... β†’ int[] internally
    int sum = 0;
    for (int n : numbers) sum += n;
    return sum;
}

main method.

// βœ… Both are valid as per JLS β€” officially accepted
Use  β†’  public static void main(String[] args)   βœ… always
                                     ─────
                               JLS recommended, Industry standard
                               IDEs auto-generate this
                               Clear and explicit

Avoid β†’  public static void main(String... args)  ⚠️
                                      ───
                               Valid but unconventional
                               Confusing β€” main takes exactly one array
                               No benefit over String[] args


Interface Methods β€” Java8 πŸ‘‰ (static, default), java9 πŸ‘‰ (private)
  • OpenJDK source, Java's own interfaces like List, Collection, Comparator, SequencedCollection use only default and static β€” NOT private.
static default private
 * Introduced in Java 8
 * Must have a body
 * Implicitly public
 * NOT inherited by implementing classes
 * Cannot be overridden (belongs to interface only)
 * Called via InterfaceName.method() only
 * Cannot use this keyword
 * Can call private static methods inside interface
 * Purpose β€” provide utility/helper 
      methods related to interface
 * Introduced in Java 8
 * Must have a body
 * Implicitly public
 * Inherited by implementing classes
 * Can be overridden by implementing classes
 * Can call private methods inside interface
 * Uses this keyword
 * Purpose β€” provide backward compatibility 
      without breaking old classes
 * Introduced in Java 9
 * Must have a body
 * Explicitly private (must write private)
 * NOT inherited by implementing classes
 * NOT accessible outside interface
 * Cannot be overridden
 * Two types β€” private and private static
 * private β†’ used by default methods
 * private static β†’ used by static methods
 * Purpose β€” avoid code duplication inside 
      interface (internal helper)

OpenJDK source, Java's own interfaces like List, Collection, Comparator, SequencedCollection use only default and static β€” NOT private.

Private methods improve code reusability inside interfaces. If two default methods need to share code, a private interface method allows them to do so without exposing that private method to implementing classes. HowToDoInJava

❌ BEFORE β€” Java 7 (Only Abstract Methods) βœ… AFTER β€” Java 8 & 9 (default + static + private)
Real Java Example β€” List.sort() added in Java 8
❌ BEFORE Java 8 β€” Old way (No sort() in List interface)
public class BeforeJava8 {
    public static void main(String[] args) {

        List<String> names = new ArrayList<>();
        names.add("Charlie");
        names.add("Alice");
        names.add("Bob");

        // ❌ Old way β€” had to use Collections utility class separately
        Collections.sort(names);  // external utility needed
        System.out.println(names); // [Alice, Bob, Charlie]

        // ❌ Old way β€” custom comparator
        Collections.sort(names, new Comparator() {
            @Override
            public int compare(String a, String b) {
                return b.compareTo(a); // reverse order
            }
        });
        System.out.println(names); // [Charlie, Bob, Alice]
    }
}
Problem:
Java team wanted to add sort() directly into the List interface in Java 8.
But millions of classes implemented List (ArrayList, LinkedList, custom lists).
Adding abstract sort() would break ALL of them!
    Why this works:
  • ArrayList, LinkedList β€” already existed, zero changes needed
  • They automatically inherit sort() default behavior
  • Any class can override it with better implementation if needed
βœ… AFTER Java 8 β€” sort() added as Default Method in List
// ─────────────────────────────────────
// ArrayList β€” Old class, NO changes made
// gets sort() for FREE via default method
// ─────────────────────────────────────
// βœ… New way β€” sort() directly on List object (Java 8 default method)
names.sort(Comparator.naturalOrder());
System.out.println("Ascending : " + names);  // [Alice, Bob, Charlie]

names.sort(Comparator.reverseOrder());
System.out.println("Descending: " + names);  // [Charlie, Bob, Alice]

// ─────────────────────────────────────
// Custom sort with lambda (Java 8)
// ─────────────────────────────────────
List<String> cities = new ArrayList<>();
cities.add("Mumbai");
cities.add("Delhi");
cities.add("Ahmedabad");
cities.add("Pune");

// sort by string length
cities.sort((a, b) -> a.length() - b.length());
System.out.println("By Length : " + cities);  // [Pune, Delhi, Mumbai, Ahmedabad]

Source Code

java.util.Comparator β€” Java 8 (uses default + static, no private) Collection interface
Comparator interface
─────────────────────────────────────────
compare()          ← abstract
naturalOrder()     ← STATIC  βœ…  called via Comparator.naturalOrder()
reverseOrder()     ← STATIC  βœ…  called via Comparator.reverseOrder()
comparing()        ← STATIC  βœ…  called via Comparator.comparing()
─────────────────────────────────────────

public interface Comparator<T> { //actual source (simplified)

    // abstract
    int compare(T o1, T o2);

    // default
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        return (c1, c2) -> {
            int res = this.compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }

    // static
    static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }
    static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
    static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor) {
        return (c1, c2) -> keyExtractor.apply(c1)
                          .compareTo(keyExtractor.apply(c2));
    }
}
Collection interface
─────────────────────────────────────────
iterator()         ← abstract
removeIf()         ← DEFAULT  βœ…  inherited, can override
clearInvalid()     ← DEFAULT  βœ…  inherited, can override
─────────────────────────────────────────

// @since 1.2
public interface List<E> extends Collection<E> {
	//...
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
	default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
}
public interface Collection<E> extends Iterable<E> {

}
// @since 1.5
public interface Iterable<T> {
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
	default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Where Private Interface Methods ARE Meant to Be Used?

Private methods improve code reusability inside interfaces. If two default methods need to share code, a private interface method allows them to do so without exposing that private method to implementing classes.

To avoid duplication, private helper methods are used inside the interface.. Available from Java 9+.

public interface Logger {

    // Default method
    default void logInfo(String msg) {
        print("INFO", msg);
    }

    // Another default method
    default void logError(String msg) {
        print("ERROR", msg);
    }

    // Static method
    static void logWarning(String msg) {
        formatAndPrint("WARNING", msg);
    }

    // Private instance method (used by default methods)
    private void print(String level, String msg) {
        System.out.println(level + ": " + msg);
    }

    // Private static method (used by static method)
    private static void formatAndPrint(String level, String msg) {
        System.out.println(level + ": " + msg);
    }
}


Comparable java.lang.Comparable: int compareTo(Object o1) vs Comparator java.util.Comparator: int compare(Object o1, Object o2) to sort collections
Feature Comparable Comparator
Package java.lang java.util
Method compareTo(T o) compare(T o1, T o2)
Sorting logic location Inside the class Outside the class
Sorting type Natural/default ordering Custom ordering
Number of sort options Only ONE Multiple
Modification required Must modify the class No class modification
Lambda support No Yes (Java 8+)
Example fields sort by id sort by id/name/age
Comparable
β†’ natural sorting (inside class)
Comparator
β†’ custom sorting (outside class, multiple ways)
/*
 * Comparable Interface (java.lang.Comparable)
 *
 * Method:
 *   int compareTo(T o)
 *
 * Key Points:
 * - The object compares itself with another object.
 * - The class must implement Comparable to define natural ordering.
 * - Allows only ONE sorting sequence.
 * - Sorting logic is written inside the class.
 * - Used for natural ordering (default sorting).
 *
 * Common classes that already implement Comparable:
 *   String, Integer, Double, Date, Calendar, etc.
 */
/*
 * Comparator Interface (java.util.Comparator)
 *
 * Method:
 *   int compare(T o1, T o2)
 *
 * Key Points:
 * - Compares TWO different objects externally.
 * - Sorting logic is written in a separate class or lambda.
 * - Allows MULTIPLE sorting sequences.
 * - Useful for customized sorting without modifying the class.
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Comparable {
private int id;
private String name;
private int age;
private long salary;

// Only ONE natural sorting sequence inside the class. Here we sort by id
@Override
public int compareTo(Employee e) {
    if (this.id > e.id) {
        return 1;
    } else if (this.id < e.id) {
        return -1;
    } else {
        return Character.toString(this.name.charAt(0))
                .compareToIgnoreCase(Character.toString(e.name.charAt(0)));
    }
}

// Multiple sorting sequences using Comparator

// Sort by Name
public static Comparator<Employee> nameComparator = new Comparator<Employee>() {
    @Override
    public int compare(Employee e1, Employee e2) {
        return e1.getName().compareTo(e2.getName());
    }
};

// Sort by ID
public static Comparator<Employee> idComparator = new Comparator<Employee>() {
    @Override
    public int compare(Employee e1, Employee e2) {
        return Integer.compare(e1.getId(), e2.getId());
    }
};

public static void main(String[] args) {
    Employee e1 = new Employee(5, "Yash", 22, 1000);
    Employee e2 = new Employee(8, "Tharun", 24, 25000);
    Employee e3 = new Employee(3, "Arun", 21, 15000);

    List<Employee> list = new ArrayList<>();
    list.add(e1);
    list.add(e2);
    list.add(e3);

    // Natural sorting using Comparable
    Collections.sort(list);
    System.out.println("Sort by Comparable (id): " + list);

    // Sorting by name using Comparator
    Collections.sort(list, Employee.nameComparator);
    System.out.println("Sort by Name Comparator: " + list);

    // Sorting by id using Comparator
    Collections.sort(list, Employee.idComparator);
    System.out.println("Sort by ID Comparator: " + list);
}

}
Java 8 Lambda Example (Comparator) Example: My stackoverflow post
List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1); // Ascending order (natural sort)
System.out.println("Ascending Order: "+ list1 ); // [1, 2, 3, 4, 5]

List list2 = Arrays.asList(5, 2, 3, 4, 1);
// Descending order using lambda Comparator
Collections.sort(list2, (a, b) -> b - a);
System.out.println("Descending Order:"+ list2 ); // [5, 4, 3, 2, 1]

public static Comparator nameComparator =
        (e1, e2) -> e1.getName().compareTo(e2.getName());
public static Comparator idComparator =
        (e1, e2) -> Integer.compare(e1.getId(), e2.getId());
java.util.Collections is a utility class consists exclusively of static methods that operate on or return collections.

Collection framework

Collection Framework is a combination of classes and interface, which is used to store and manipulate the data in the form of objects.

interface Collection<E>extends Iterable

  • Iterator is a fail-fast in nature. i.e it throws ConcurrentModificationException if a collection is modified while iterating other than it’s own remove() method.
  • Where as Enumeration is fail-safe in nature. It doesn’t throw any exceptions if a collection is modified while iterating.
fail-fast vs fail-safe Iterator
Fail-Fast Iterators (Not thread-safe)
Works on original collection - throws ConcurrentModificationException
Performance: Faster (no cloning overhead)
Vector is FAIL-FAST, despite being thread-safe!
ArrayList
Fail-Safe Iterators (Thread-safe)
Works on cloned copy
Performance: Slower (cloning overhead)
CopyOnWriteArrayList
    Why Vector is Fail-Fast Despite Being Thread-Safe (Thread-Safe β‰  Fail-Safe)
  • Vector's synchronization: Individual methods (add(), get()) are synchronized, preventing corruption
  • Iterator is NOT synchronized: Iterator checks modCount and throws exception if collection is modified
  • Synchronization doesn't prevent modification during iteration: It only prevents data corruption

Hashtable traverse using Enumeration is Fail-Safe, Iterator is Fail-Fast.
Behavior: Immediately throw ConcurrentModificationException if the collection is modified while iterating (except through the iterator's own methods).
How it works: Maintains a modCount (modification count) that tracks structural changes. The iterator checks if this count has changed during iteration.
Examples: ArrayList, HashMap, HashSet, Most collections in java.util package
Use case: When you want to detect concurrent modifications early and fail immediately.
javaList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
    String item = iterator.next();
    list.add("D"); // Throws ConcurrentModificationException
}
      
Example:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// Thread 1: Iterating
Thread t1 = new Thread(() -> {
    try {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println("Thread 1: " + iterator.next());
            Thread.sleep(100); // Simulate work
        }
    } catch (ConcurrentModificationException e) {
        System.out.println("Thread 1: ConcurrentModificationException caught!");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
// Thread 2: Modifying (changes modCount)
Thread t2 = new Thread(() -> {
    try {
        Thread.sleep(150);
        //list.add("E"); // Increments modCount
        list.add(4, "E"); // No-Increments modCount - even T1 leads to ConcurrentModificationException
        System.out.println("Thread 2: Added element E");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
t1.start();
t2.start();
/* output
Thread 1: A
Thread 1: B
Thread 2: Added element E
Thread 1: ConcurrentModificationException caught!
*/

Behavior: Work on a cloned copy of the collection. No exception is thrown even if the collection is modified during iteration.
How it works: Creates a snapshot/clone of the collection at the time the iterator is created. Changes to the original collection don't affect the iteration.
Examples: CopyOnWriteArrayList, ConcurrentHashMap, Collections in java.util.concurrent package
Use case: When you need thread-safe iteration in concurrent environments.
javaList<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
    String item = iterator.next();
    list.add("D"); // No exception - iterator works on snapshot
}
      
Example:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// Thread 1: Iterating
Thread t1 = new Thread(() -> {
    try {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println("Thread 1: " + iterator.next());
            Thread.sleep(100); // Simulate work
        }
    } catch (ConcurrentModificationException e) {
        System.out.println("Thread 1: ConcurrentModificationException caught!");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
// Thread 2: Modifying (changes modCount)
Thread t2 = new Thread(() -> {
    try {
        Thread.sleep(150);
        list.add("E"); // Increments modCount
        System.out.println("Thread 2: Added element E");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
t1.start();
t2.start();
/* output
Thread 1: A
Thread 1: B
Thread 2: Added element E
Thread 1: C
Thread 1: D
*/
package java.util; JDK1.2 πŸ‘‰HashMap vs JDK1.0 πŸ‘‰Hashtable vs JDK5 πŸ‘‰ConcurrentHashMap - Complete Comparison

ConcurrentHashMap: Maps the specified key to the specified value in this table. Neither the key nor the value can be null

java.util.HashMap (Java 1.2) java.util.Hashtable (Java 1.0 legacy) java.util.concurrent.ConcurrentHashMap (Java 1.5)
// HashMap - Allows null key and values
Map hashMap = new HashMap<>();
hashMap.put(null, "value1");        // βœ… OK
hashMap.put("key1", null);          // βœ… OK
hashMap.put("key2", null);          // βœ… OK (multiple nulls)
System.out.println(hashMap);
// Output: {null=value1, key1=null, key2=null}
      
// Hashtable - Does NOT allow null key or values
Map hashtable = new Hashtable<>();
hashtable.put(null, "value1");      // ❌ NullPointerException
hashtable.put("key1", null);        // ❌ NullPointerException
      
// ConcurrentHashMap - Does NOT allow null key or values
Map concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put(null, "value1");  // ❌ NullPointerException
concurrentMap.put("key1", null);    // ❌ NullPointerException
      
How it works:
─────────────────────────────────────────
Step 1: key.hashCode()          β†’ generates hash number
Step 2: hash % array.length     β†’ finds bucket index
Step 3: store in that bucket    β†’ LinkedList if collision
Step 4: LinkedList > 8 nodes    β†’ converts to Red-Black Tree
─────────────────────────────────────────
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

System.out.println("=== Iterator (Fail-Fast) ===");
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
    String key = iterator.next();
    System.out.println(key + " = " + map.get(key));
// Modifying during iteration
if (key.equals("A")) {
    map.put("D", 4); // Modifies modCount
}

}
/*
=== Iterator (Fail-Fast) ===
A = 1
Exception in thread "main" java.util.ConcurrentModificationException
*/
Hashtable<String, Integer> table = new Hashtable<String, Integer>() {{
    put("A", 1);
    put("B", 2);
    put("C", 3);
}}; // java 9+ = new Hashtable<>(Map.of("A", 1, "B", 2, "C", 3));

System.out.println("=== Iterator (Fail-Fast) ===");
try {
    Iterator<String> iterator = table.keySet().iterator();
    while (iterator.hasNext()) {
        String key = iterator.next();
        System.out.println(key);
        if (key.equals("A")) {
            table.put("D", 4); // Throws exception
        }
    }
} catch (ConcurrentModificationException e) {
    System.out.println("Iterator: ConcurrentModificationException!");
}

System.out.println("\n=== Enumeration (NOT Fail-Fast) ===");
Enumeration<String> enumeration = table.keys();
while (enumeration.hasMoreElements()) {
    String key = enumeration.nextElement();
    System.out.println(key);
    if (key.equals("A")) {
        table.put("D", 4); // May cause unpredictable behavior
    }
}
System.out.println("Enumeration completed (no exception)");

/* output
=== Iterator (Fail-Fast) ===
A
Iterator: ConcurrentModificationException!
=== Enumeration (NOT Fail-Fast) ===
A
D
C
B
Enumeration completed (no exception)
*/
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

Iterator<String> iterator = map.keySet().iterator();

while (iterator.hasNext()) {
    String key = iterator.next();
    System.out.println(key + " = " + map.get(key));
// Modifying during iteration - NO EXCEPTION
if (key.equals("A")) {
    map.put("D", 4);
    System.out.println("Added D=4 during iteration");
}

}

System.out.println("\nFinal map: " + map);
System.out.println("No exception thrown!");
/*
A = 1
Added D=4 during iteration
B = 2
C = 3
D = 4
Final map: {A=1, B=2, C=3, D=4}
No exception thrown!
*/

Performance Example Metrix output
public class ThreadSafetyComparison {
    /** Number of CPUS, to place bounds on some sizings */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    private static final int THREAD_COUNT = 16; //NCPU=12
    private static final int OPERATIONS = 1000;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("THREAD_COUNT :"+THREAD_COUNT);
        System.out.println("=== HashMap (Not Thread-Safe) ===");
        testMap(new HashMap<>());
 
        System.out.println("\n=== Hashtable (Thread-Safe - Synchronized) ===");
        testMap(new Hashtable<>());

        System.out.println("\n=== ConcurrentHashMap (Thread-Safe - Lock Striping) ===");
        testMap(new ConcurrentHashMap<>());
    }
    private static void testMap(Map<Integer, Integer> map) throws InterruptedException {
        long startTime = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];

        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATIONS; j++) {
                    int key = threadId * OPERATIONS + j;
                    map.put(key, key);
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("Expected size: " + (THREAD_COUNT * OPERATIONS));
        System.out.println("Actual size: " + map.size());
        System.out.println("Time taken: " + (endTime - startTime) + "ms");
        System.out.println("Data consistency: " + 
            (map.size() == THREAD_COUNT * OPERATIONS ? "βœ… PASS" : "❌ FAIL"));
    }
}
      
THREAD_COUNT :4
THREAD_COUNT :4
=== HashMap (Not Thread-Safe) ===
Expected size: 4000
Actual size: 3921
Time taken: 24ms
Data consistency: ❌ FAIL
=== Hashtable (Thread-Safe - Synchronized) ===
Expected size: 4000
Actual size: 4000
Time taken: 3ms
Data consistency: βœ… PASS
=== ConcurrentHashMap (Thread-Safe - Lock Striping) ===
Expected size: 4000
Actual size: 4000
Time taken: 8ms
Data consistency: βœ… PASS
      
THREAD_COUNT :12
THREAD_COUNT :12
=== HashMap (Not Thread-Safe) ===
Expected size: 12000
Actual size: 10623
Time taken: 31ms
Data consistency: ❌ FAIL
=== Hashtable (Thread-Safe - Synchronized) ===
Expected size: 12000
Actual size: 12000
Time taken: 15ms
Data consistency: βœ… PASS
=== ConcurrentHashMap (Thread-Safe - Lock Striping) ===
Expected size: 12000
Actual size: 12000
Time taken: 12ms
Data consistency: βœ… PASS
      
THREAD_COUNT :16
THREAD_COUNT :16
=== HashMap (Not Thread-Safe) ===
Expected size: 16000
Actual size: 15293
Time taken: 30ms
Data consistency: ❌ FAIL
=== Hashtable (Thread-Safe - Synchronized) ===
Expected size: 16000
Actual size: 16000
Time taken: 12ms
Data consistency: βœ… PASS
=== ConcurrentHashMap (Thread-Safe - Lock Striping) ===
Expected size: 16000
Actual size: 16000
Time taken: 22ms
Data consistency: βœ… PASS
      

image

ConcurrentHashMap is thread-safe NOT via synchronization, but via segment-level locking (Java 7) and CAS with bucket-level locking (Java 8+) β€” allowing multiple threads to write in parallel without blocking each other.

How it actually works

Before Java 8 β€” Segment Locking Java 8+ β€” CAS + Node Level Locking
ConcurrentHashMap
β”œβ”€β”€ Segment[0]  πŸ”’  ← locks only this
β”œβ”€β”€ Segment[1]  
β”œβ”€β”€ Segment[2]  πŸ”’  ← locks only this
└── Segment[15]

Map divided into 16 segments by default
Each segment is an independent lock
16 threads can write simultaneously β€” one per segment
ConcurrentHashMap
β”œβ”€β”€ Node[0]  πŸ”’  ← locks only this bucket
β”œβ”€β”€ Node[1]  
β”œβ”€β”€ Node[2]  πŸ”’  ← locks only this bucket
└── Node[n]

Locks at individual bucket (node) level
Uses CAS (Compare-And-Swap) for lock-free reads
Even more granular than segments
Java 8 ConcurrentHashMap - Node Level Lock Explanation with Example
ConcurrentMap - MultiThread Output
public class ConcurrentMapThreadDemo {
    @AllArgsConstructor
    static class Data {
        int value;
        @Override
        public String toString() {
            return "Data{value=" + value +
                    ", identityHash=" + System.identityHashCode(this) + "}";
        }
    }
static ConcurrentHashMap<String, Data> map = new ConcurrentHashMap<>();

// approximate bucket index (like internal spread)
static int bucketIndex(String key, int tableSize) {
    int h = key.hashCode();
    h ^= (h >>> 16);   // spread hash
    return (tableSize - 1) & h;
}
static void putKey(String key, int value) {
    Data data = new Data(value);
    if ( map.containsKey(key) ) {
        System.err.println(Thread.currentThread().getName() +
                " GET key=" + key + " val=" + map.get(key)
        );
    }
    map.put(key, data);

    int bucket = bucketIndex(key, 16); // default table approx
    System.out.println(
            Thread.currentThread().getName() +
            " PUT key=" + key + " val=" + value +
            " hash=" + key.hashCode() + " bucket=" + bucket +
            " identityHash=" + System.identityHashCode(data)
    );
}

public static void main(String[] args) throws Exception {
    System.out.println("Initial inserting 4 keys\n");
    for (int i = 1; i <= 4; i++) {
        putKey("T1_Key_" + i, i);
    }

    Thread t1 = new Thread(() -> {
        for (int i = 1; i <= 6; i++) {
            //Thread.sleep(3500);
            putKey("T1_Key_" + i, (i * 10));
        }
    }, "Thread-1");

    Thread t2 = new Thread(() -> {
        //for (int i = 6; i >= 1; i--) {
        for (int i = 1; i <= 6; i++) {
            putKey("T1_Key_" + i, (i * 100));
        }
    }, "Thread-2");

    t1.start();    t2.start();
    t1.join();     t2.join();

    System.out.println("\nFinal Map Size = " + map.size());
}

}
Initial inserting 4 keys
main PUT key=T1_Key_1 val=1 hash=-597618225 bucket=14 identityHash=1288141870
main PUT key=T1_Key_2 val=2 hash=-597618224 bucket=1 identityHash=2054881392
main PUT key=T1_Key_3 val=3 hash=-597618223 bucket=0 identityHash=966808741
main PUT key=T1_Key_4 val=4 hash=-597618222 bucket=3 identityHash=1908153060
Thread-1 GET key=T1_Key_1 val=Data{value=1, identityHash=1288141870}
Thread-2 GET key=T1_Key_1 val=Data{value=1, identityHash=1288141870}
Thread-1 PUT key=T1_Key_1 val=10 hash=-597618225 bucket=14 identityHash=1032642556
Thread-2 PUT key=T1_Key_1 val=100 hash=-597618225 bucket=14 identityHash=1195851372
Thread-1 GET key=T1_Key_2 val=Data{value=2, identityHash=2054881392}
Thread-1 PUT key=T1_Key_2 val=20 hash=-597618224 bucket=1 identityHash=860567101
Thread-2 GET key=T1_Key_2 val=Data{value=20, identityHash=860567101}
Thread-1 GET key=T1_Key_3 val=Data{value=3, identityHash=966808741}
Thread-2 PUT key=T1_Key_2 val=200 hash=-597618224 bucket=1 identityHash=1233176820
Thread-1 PUT key=T1_Key_3 val=30 hash=-597618223 bucket=0 identityHash=930395276
Thread-2 GET key=T1_Key_3 val=Data{value=30, identityHash=930395276}
Thread-1 GET key=T1_Key_4 val=Data{value=4, identityHash=1908153060}
Thread-2 PUT key=T1_Key_3 val=300 hash=-597618223 bucket=0 identityHash=1304384796
Thread-2 GET key=T1_Key_4 val=Data{value=40, identityHash=1920351238}
Thread-1 PUT key=T1_Key_4 val=40 hash=-597618222 bucket=3 identityHash=1920351238
Thread-2 PUT key=T1_Key_4 val=400 hash=-597618222 bucket=3 identityHash=146653903
Thread-2 GET key=T1_Key_5 val=Data{value=50, identityHash=702447243}
Thread-1 PUT key=T1_Key_5 val=50 hash=-597618221 bucket=2 identityHash=702447243
Thread-2 PUT key=T1_Key_5 val=500 hash=-597618221 bucket=2 identityHash=1987525098
Thread-1 PUT key=T1_Key_6 val=60 hash=-597618220 bucket=5 identityHash=1129205587
Thread-2 GET key=T1_Key_6 val=Data{value=60, identityHash=1129205587}
Thread-2 PUT key=T1_Key_6 val=600 hash=-597618220 bucket=5 identityHash=964659384
Final Map Size = 6
,source>Java8 Source Code
public V put(K key, V value) {
    return putVal(key, value, false);
}
public V putIfAbsent(K key, V value) {
    return putVal(key, value, true);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node[] tab = table;;) {
        Node f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node(hash, key, value)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent // check first node without acquiring lock
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node(hash, key, value);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        //...
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (binCount != 0) {
                //...
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

ConcurrentHashMap operations are thread-safe individually, but containsKey() + put() is not atomic, so another thread may update the key between those calls, causing the behavior you observed.

Race condition between containsKey() and put()
Even if you use:
if(map.containsKey(key)) {
    map.get(key);
}
map.put(key,value);
another thread may insert between them:
T1 containsKey(K) β†’ false
T2 put(K)
T1 put(K)
So T1 never sees T2's value. This is called a race condition.
ConcurrentHashMap locking works like this: (node-level locking)
Empty bucket β†’ CAS insert
Non-empty bucket β†’ synchronized(head node)

Threads can still update different buckets concurrently.
ConcurrentHashMap

table[]
   β”‚                    Thread behavior:
   β”œβ”€β”€ bucket0 β†’ Node     Thread1 β†’ bucket2 πŸ”’
   β”œβ”€β”€ bucket1 β†’ Node     Thread2 β†’ bucket7 πŸ”’
   β”œβ”€β”€ bucket2 β†’ Node     Thread3 β†’ bucket11 πŸ”’
   └── bucketN        

For such cases atomic methods like putIfAbsent() or compute() should be used.

synchronized HashMap ConcurrentHashMap
Lock scope Entire map πŸ”’ One bucket/segment
Concurrent writers ❌1 at a time βœ… Many simultaneously
How synchronized keyword CAS + node locks
Performance Slow under contention High throughput

ConcurrentHashMap does not allow null keys or values to avoid ambiguity during concurrent access because get() returning null cannot distinguish between "key not present" and "value is null".

Main Reason: Avoid Ambiguity During Concurrent Access

HashMap (single-threaded) ConcurrentHashMap (multi-threaded)
β†’ No null (to avoid ambiguity)
// null key maps to bucket[0]
map.put(null, "hello"); // βœ… works
map.put("k",  null);    // βœ… works

// Safe in single thread:
if (map.containsKey("k")) {
  V v = map.get("k"); // safe
  // you control timing
}
map.put(null, "val"); // ❌ NullPointerException
map.put("k",  null); // ❌ NullPointerException

// WHY? Race between check + get:
if (map.containsKey("k")) { // Thread A checks β†’ true
  // Thread B removes "k" HERE!
  V v = map.get("k"); // Thread A gets β†’ null πŸ’₯

Cannot determine null because:
1. key was removed?     β†’ null
2. key exists but value was null?      β†’ null
   IMPOSSIBLE to distinguish safely!
}
// containsKey + get is NOT atomic
// Another thread can act in between
HashMap vs ConcurrentHashMap: Java 7 and Java 8+ Internal Comparison
HashMap (Single-threaded)
Java 7 Source
Java 8
ConcurrentHashMap (Multi-threaded - Before Java 8)
Java 7 Source
ConcurrentHashMap (Java 8+)
Java 8+ Source
πŸ”’ Hash Spreading πŸ”’ Hash Spreading πŸ”’ Hash Spreading
/** * Java 7 HashMap hash spreading */
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
/** * Java 8 HashMap hash spreading */
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

Used before bucket index calculation.

/** * Spread hash across segments */
private static int hash(int h) {
    // Spread bits to regularize both segment and index locations,
    // using variant of single-word Wang/Jenkins hash.
    h += (h <<  15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h <<   3);
    h ^= (h >>>  6);
    h += (h <<   2) + (h << 14);
    return h ^ (h >>> 16);
}
Helps distribute keys across segments.
/** * Fast hash spreading */
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}
Reduces collisions and improves performance.
πŸ“ Bucket Index Calculation πŸ“ Bucket Index Calculation πŸ“ Bucket Index Calculation
// Java 7
int index = (tab.length - 1) & hash;
// table = new Entry[DEFAULT_INITIAL_CAPACITY];
// Java 8
int index = (n - 1) & root.hash; // Node[] tab, TreeNode root
Requires table size to be power of two.
int index = (seg.table.length - 1) & hash;
//
static int indexFor(int h, int length) {
    return h & (length-1);
}
Computed inside each Segment.
int index = (n - 1) & hash;
Used for fast bit masking.
🌳 Collision Handling 🌳 Collision Handling 🌳 Collision Handling
Java 7
 Entry -> Entry -> Entry 
Java 8+
 Entry -> TreeNode 
Uses Red-Black Tree when bucket grows.
 HashEntry ↓ HashEntry ↓ HashEntry 

Only linked list.

No tree structure.

 Node -> Node -> Node 

If bucket β‰₯ 8

 TreeNode (Red-Black Tree) 

Improves worst-case lookup.

🌳 Treeify Threshold
(Treeification was introduced in Java8)
🌳 Treeify Threshold 🌳 Treeify Threshold
(Treeification was introduced in Java8)
// Java 7 does NOT support treeification at all.
// Buckets are always linked lists, so there is no TREEIFY_THRESHOLD.
//
// Java8
static final int TREEIFY_THRESHOLD = 8;
if bucket size >= 8
    convert linked list β†’ red-black tree
static final int UNTREEIFY_THRESHOLD = 6; 
When resizing/shrinking:
if bucket size <= 6
    convert tree β†’ linked list
static final int MIN_TREEIFY_CAPACITY = 64;
Important rule: 
More buckets reduces collisions better than building trees
if table size < 64
    DO NOT treeify
    instead resize table

bucket
A β†’ B β†’ C β†’ D β†’ E β†’ F β†’ G β†’ H
          (8 nodes)

table size β‰₯ 64
↓
convert to

        D
      /   \
     B     F
    / \   / \
   A  C  E   G
                \
                 H
❌ Not supported
// Java 7 does NOT support treeification at all.
// Buckets are always linked lists, so there is no TREEIFY_THRESHOLD.
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
Same values as HashMap.
πŸ“ˆ Resize Trigger πŸ“ˆ Resize Trigger πŸ“ˆ Resize Trigger
 if (size > threshold) resize(); 

Threshold:

// java 7 Treeify Threshold
threshold = (int)(capacity * loadFactor); 

Default

 capacity = 16 loadFactor = 0.75 

Resize occurs per Segment.

 Segment.rehash() 

Only the locked segment grows.

Resize controlled by:

 sizeCtl 

Determines when to resize.

⚠️ Resize Behavior ⚠️ Resize Behavior ⚠️ Resize Behavior
 1 create new table
 2 rehash all entries
 3 move nodes 

⚠️ Java 7 issue:

Concurrent resize may create cyclic linked list.

 Segment locked
 Segment resized
 Entries moved 

Other segments unaffected.

Uses cooperative resizing.

Multiple threads move entries.

Internal structures:

 ForwardingNode
 nextTable
 transferIndex 
πŸ“Š Size Tracking πŸ“Š Size Tracking πŸ“Š Size Tracking
 transient int size; 

Simple counter.

Not thread safe.

Each segment tracks count.

 Segment.count 

Total size = sum of segments.

Striped counters.

 baseCount CounterCell[] 

Reduces contention.

πŸ” Iterator Behavior πŸ” Iterator Behavior πŸ” Iterator Behavior

Fail-Fast iterator.

 modCount 

Modification during iteration throws:

 ConcurrentModificationException 

Weakly consistent iterator.

No exception.

May reflect partial updates.

Weakly consistent iterator.

Allows concurrent modifications.

🧩 Node Types 🧩 Node Types 🧩 Node Types
 Entry TreeNode (Java 8+) 
 HashEntry 
Node           // regular node
TreeNode       // red-black tree node
TreeBin        // tree container
ForwardingNode // during resize
ReservationNode // computeIfAbsent

Used for trees and resizing.

βš™οΈ Compute APIs βš™οΈ Compute APIs βš™οΈ Compute APIs
compute()
computeIfAbsent()
computeIfPresent()
merge() 

Not thread safe.

Supported but rely on segment locking.

Atomic operations.

compute()
computeIfAbsent()
merge() 

Executed with bucket synchronization.

πŸ—‚οΈ Storage Structure πŸ—‚οΈ Storage Structure πŸ—‚οΈ Storage Structure
// Single flat bucket array
transient Entry<K,V>[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;              // not volatile
    Entry<K,V> next;
    final int hash;
    Entry(int h, K k, V v, Entry<K,V> n) {
        key = k;
        value = v;
        next = n;
        hash = h;
    }
}
// Array of Segments (default 16)
final Segment<K,V>[] segments;
static class Segment<K,V> extends ReentrantLock
              implements Serializable {
    transient volatile HashEntry<K,V>[] table;
    transient int threshold;
    final float loadFactor;
    Segment(float lf, int threshold,
            HashEntry<K,V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }
}
static final class HashEntry<K,V> {
    final int hash;
    final K key;
    volatile V value;     // visibility across threads
    volatile HashEntry<K,V> next;
}
// Flat Node array (similar to HashMap)
// but supports CAS + volatile
transient volatile Node<K,V>[] table;
private transient volatile Node<K,V>[] nextTable;
static class Node<K,V>
implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}
// Segment kept only for serialization compatibility
static class Segment<K,V> extends ReentrantLock
            implements Serializable {
    final float loadFactor;
    Segment(float lf) {
        this.loadFactor = lf;
    }
}
βž• put("apple", 1) Flow βž• put("apple", 1) Flow βž• put("apple", 1) Flow
// Step 1: hash the key
int hash = hash("apple");
// Step 2: find bucket index
int i = indexFor(hash, table.length);
// Step 3: walk linked list
for (Entry e = table[i];
     e != null; e = e.next) {
    if (e.hash == hash && e.key.equals("apple")) {
        e.value = 1; // overwrite
        return;
    }
}
// Step 4: insert new entry
// ⚠️ NO LOCK (not thread-safe)
table[i] = new Entry(hash, "apple", 1, table[i]);
// Step 1: compute hash
int hash = hash("apple");
// Step 2: find segment
Segment<K,V> seg = segmentFor(hash);
// Step 3: lock that segment only
seg.lock();
try {
int i = indexFor(hash, seg.table.length);
HashEntry e = seg.table[i];
while (e != null) {
if (e.hash == hash && e.key.equals("apple")) {
e.value = 1;
return;
}
e = e.next;
}
seg.table[i] =
new HashEntry(
hash,"apple",1,
seg.table[i]);
} finally {
seg.unlock();
}
// Step 1: spread hash
int hash = spread("apple".hashCode());
// Step 2: find bucket
int i = (n - 1) & hash;
// Step 3: read head node
Node<K,V> f = tabAt(table, i);
if (f == null) {
    // CAS insertion
    casTabAt(table, i, null,
        new Node(hash,"apple",1,null));
} else {
    // lock only head node
    synchronized (f) {
        if (tabAt(table, i) == f) {
            Node e = f;
            while (e.next != null) {
                if (e.hash == hash
                    && e.key.equals("apple")) {
                    e.val = 1;
                    return;
                }
                e = e.next;
            }
            e.next =
            new Node(hash,"apple",1,null);
        }
    }
    // Convert to Tree if bucket >= 8
    treeifyBin(table, i);
}
πŸ”’ Locking
None β€” Not thread safe
πŸ”’ Locking
ReentrantLock per Segment
Maximum β‰ˆ 16 concurrent writers
πŸ”’ Locking
CAS (empty bucket)
synchronized on head node
null key/value
βœ… Allowed
null key/value
❌ Not allowed
null key/value
❌ Not allowed
// HashMap internal Entry structure (Java 7)
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;
    // Creates a new entry
    Entry(int h, K k, V v, Entry<K,V> n) {
        key = k;
        value = v;
        next = n;
        hash = h;
    }
}
// Entry set view
private final class EntrySet
        extends AbstractSet<Map.Entry<K,V>> { }

Example

HashMap<String,Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put(null, 30);   // HashMap allows null key
System.out.println(map.get("Apple"));
// The segments, each of which is a specialized hash table.
final Segment<K,V>[] segments;
//Segment uses locking mechanism (extends ReentrantLock)
static class Segment<K,V>
        extends ReentrantLock implements Serializable {
    private static final long serialVersionUID =
        2249069246763182397L;
    transient volatile HashEntry<K,V>[] table;
    transient int threshold;
    final float loadFactor;
    Segment(float lf, int threshold,
            HashEntry<K,V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }
}
final class EntrySet
        extends AbstractSet<Map.Entry<K,V>> { }

Example

ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
// map.put(null,30); ❌ throws NullPointerException
// map.put(10,null); ❌ throws NullPointerException
// Segment class retained only for serialization compatibility
static class Segment<K,V>
        extends ReentrantLock implements Serializable {
    private static final long serialVersionUID =
        2249069246763182397L;
    final float loadFactor;
    Segment(float lf) {
        this.loadFactor = lf;
    }
}
/* -------- Node structure -------- */
// Key-value entry (Java 8+)
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}
// Main table storing nodes
transient volatile Node<K,V>[] table;
// Used during resizing
private transient volatile Node<K,V>[] nextTable;

Example

ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
// map.put(null,10); ❌ Not allowed
// map.put("Orange", null); ❌ Not allowed
TreeSet/TreeMap & HashSet/HashMap β€” Data Structure & Null Values

HashSet / HashMap TreeSet/TreeMap
Internal Data Structure β€” Array + LinkedList + Red-Black Tree
HashMap  = Hash + Map  β†’ key-value  + null key βœ…
HashSet  = Hash + Set  β†’ only keys  + null βœ…

How it works:
─────────────────────────────────────────
Step 1: key.hashCode()          β†’ generates hash number
Step 2: hash % array.length     β†’ finds bucket index
Step 3: store in that bucket    β†’ LinkedList if collision
Step 4: LinkedList > 8 nodes    β†’ converts to Red-Black Tree
─────────────────────────────────────────
Why HashMap allows null key?

// HashMap handles null key specially:
static final int hash(Object key) {
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// null key always goes to bucket index 0 β€” special hardcoded handling
Internal Data Structure β€” Red-Black Tree (always)
TreeMap  = Tree + Map  β†’ key-value  + NO null key ❌
TreeSet  = Tree + Set  β†’ only keys  + NO null ❌

How it works:
─────────────────────────────────────────
Step 1: compare(newKey, rootKey)   β†’ left if smaller, right if bigger
Step 2: insert and auto-balance    β†’ Red-Black Tree rules
Step 3: always maintains sorted order
─────────────────────────────────────────
Why TreeMap/TreeSet doesn't allow null key?

// TreeMap internally does this for every insert:
key.compareTo(anotherKey)

// if key is null:
null.compareTo("Alice")  // ❌ NullPointerException!
// null has no compareTo method β€” cannot be compared β€” cannot be placed in tree

Complete Summary Table

Feature HashMap HashSet TreeMap TreeSet
Internal Structure Array + LinkedList + RBTree Array + LinkedList + RBTree Red-Black Tree Red-Black Tree
Null Key βœ… One allowed β€” ❌ NPE β€”
Null Value βœ… Many allowed βœ… One allowed βœ… allowed ❌ NPE
Ordering ❌ No order ❌ No order βœ… Sorted by key βœ… Sorted
Duplicates ❌ No dup keys ❌ No dups ❌ No dup keys ❌ No dups
Performance O(1) avg O(1) avg O(log n) O(log n)
Thread Safe ❌ No ❌ No ❌ No ❌ No
Based On HashMap β€” NavigableMap TreeMap

ArrayList Default to empty-array, The default capacity of 10 is NOT allocated immediately.
    It's allocated lazily on the first add() operation.
  • ArrayList grows by approximately 50% (1.5x) of current capacity, not when it reaches size 10.
  • Capacity: 15 β†’ 22 (15 + 15/2 = 22, rounded down from 22.5)
  • System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    //The number of times this list has been <i>structurally modified</i>.
    protected transient int modCount = 0;
}
public class Vector<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    public Vector() {
        this(10);
    }
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
}
public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final int DEFAULT_CAPACITY = 10;
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    // The size of the ArrayList (the number of elements it contains).
    private int size;
    private Object[] grow() {
        return grow(size + 1);
    }
    private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
    }
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        // ...
    }
}

Garbage Collection (GC)

Primary Purpose: Automatic memory management - the GC identifies and removes objects that are no longer reachable/referenced, freeing up memory for new objects.

class System (sourece)
public final class System {
    /** Don't let anyone instantiate this class */
    private System() {
    }

    public final static PrintStream out = null;
    public final static PrintStream err = null;

    private static volatile Console cons = null;
     // @since   1.6
     public static Console console() {
         if (cons == null) {
             synchronized (System.class) {
                 cons = sun.misc.SharedSecrets.getJavaIOAccess().console();
             }
         }
         return cons;
     }

    public static native long currentTimeMillis();
     //@since 1.5
    public static native long nanoTime();

    /** Copies an array from the specified source array, beginning at the
     * specified position, to the specified position of the destination array.
     * A subsequence of array components are copied from the source
     * array referenced by <code>src</code> to the destination array
     * referenced by <code>dest</code>. The number of components copied is
     * equal to the <code>length</code> argument. The components at
     * positions <code>srcPos</code> through */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
    private static Properties props;
    private static native Properties initProperties(Properties props);
    public static String lineSeparator() {
        return lineSeparator;
    }

    private static String lineSeparator;
    public static void setProperties(Properties props) {
        //...
    }
    public static String getProperty(String key) {
        // ...
    }
    public static Properties getProperties() {
        //...
    }
    public static String setProperty(String key, String value) {
        //...
    }

    /** Removes the system property indicated by the specified key.
     * @since 1.5 */
    public static String clearProperty(String key) {
        checkKey(key);
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new PropertyPermission(key, "write"));
        }

        return (String) props.remove(key);
    }

    // Terminates the currently running Java Virtual Machine. The
    public static void exit(int status) {
        Runtime.getRuntime().exit(status);
    }

    /** Calling the <code>gc</code> method suggests that the Java Virtual
     * Machine expend effort toward recycling unused objects in order to
     * make the memory they currently occupy available for quick reuse.
     * When control returns from the method call, the Java Virtual
     * Machine has made a best effort to reclaim space from all discarded
     * objects. */
    public static void gc() {
        Runtime.getRuntime().gc();
    }
    @CallerSensitive
    public static void load(String filename) {
        Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
    }
    // ...
}
Object: Created based on the class template using a constructor.
Real-world objects contain state and behavior. state is stored in fields and behavior is exposed through methods.
public class Object {
    // The {@code Class} object that represents the runtime class of this object.
    public final native Class<?> getClass();
    protected native Object clone() throws CloneNotSupportedException;
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    /** Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or
     * some other thread interrupts the current thread, or a certain
     * amount of real time has elapsed. */
    public final void wait(long timeout, int nanos) throws InterruptedException {
        //...
    }
    public final void wait() throws InterruptedException {
        wait(0);
    }
    public final native void wait(long timeout) throws InterruptedException;
    /** Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation. A thread waits on an object's
     * monitor by calling one of the {@code wait} methods. */
    public final native void notify();

    /** Wakes up all threads that are waiting on this object's monitor. A
     * thread waits on an object's monitor by calling one of the {@code wait} methods. */
    public final native void notifyAll();

   /**
     * Called by the garbage collector on an object when garbage collection
     * determines that there are no more references to the object.
     * A subclass overrides the {@code finalize} method to dispose of
     * system resources or to perform other cleanup. */
    protected void finalize() throws Throwable { }
}
πŸš€ Garbage Collector (GC), External Call System.gc() is just a request. JVM decides the clean



What is GC?

  • Automatically removes unused objects from Heap
  • Runs in background via GC thread
  • All application threads pause during GC β†’ called "Stop The World (STW)"
  • Cannot be forced β€” System.gc() is just a request, JVM decides
    • System.gc() Internally calls Runtime.getRuntime().gc() β€” both are same
    • Calling it explicitly is bad practice in production code. JVM may ignore it β€” no guarantee it will run
Person p = new Person();  new Person() β†’ Heap, 'p' reference β†’ Stack
p = null;                 eligible for GC β€” no reference pointing to object

Simple Rule:
──────────────────────────────────────────
Objects (Heap)     β†’ GC cleans            βœ…
Classes (Metaspace)β†’ GC cleans (Full GC)  βœ…
Methods (Stack)    β†’ JVM auto cleans      ❌
Instructions (PC)  β†’ JVM auto cleans      ❌
Native (C/C++)     β†’ OS/manual cleans     ❌
──────────────────────────────────────────

Summary Table

Memory Area GC Applies? Cleaned By Error if Full
Heap (Young + Old) βœ… Yes Minor / Major / Full GC OutOfMemoryError
Metaspace βœ… Yes (partial) Full GC only OutOfMemoryError: Metaspace
Stack ❌ No JVM auto (LIFO) StackOverflowError
PC Register ❌ No JVM auto β€”
Native Method Stack ❌ No OS / JNI manually StackOverflowError
Object eligible for GC, finalize() GC Types, Algorithms, Flags
Object eligible for GC when:
 * No references pointing to it
 * Reference set to null
 * Reference out of scope
 * Island of isolation (objects pointing to each other but no outside reference)

What causes frequent GC?
 * Too small heap                (-Xmx too low)
 * Memory leak                   (references held unnecessarily)
 * Large object creation         (directly goes to Old Generation)
 * Poor object reuse             (creating new objects in loops)
 * Static references             (held for entire app lifetime)

Finalization (Deprecated Java 9+, Removed Java 18+)
 * finalize() called before GC removes object β€” unreliable, avoid
 * Use try-with-resources or Cleaner API instead
GC Types
 * Minor GC β†’ cleans Young Generation (Eden + Survivors) β€” fast, frequent
 * Major GC β†’ cleans Old Generation                      β€” slow, infrequent
 * Full GC  β†’ cleans Entire Heap + Metaspace             β€” slowest, STW pause

GC Algorithms (JVM options)
 * Serial GC         β†’ single thread, simple         (-XX:+UseSerialGC)
 * Parallel GC       β†’ multiple threads, throughput  (-XX:+UseParallelGC)
 * G1 GC             β†’ default Java 9+, balanced     (-XX:+UseG1GC)
 * ZGC               β†’ low latency, Java 15+         (-XX:+UseZGC)
 * Shenandoah        β†’ low pause, Java 12+           (-XX:+UseShenandoahGC)

JVM Heap Size Flags
 -Xms β†’ initial heap size        e.g. -Xms256m
 -Xmx β†’ maximum heap size        e.g. -Xmx1024m
 -Xmn β†’ young generation size    e.g. -Xmn256m

Java-1.2 πŸ‘‰ Purpose of public abstract class java.lang.ref.Reference { }



Reference<T> is the base class for special reference types used by the Garbage Collector.

It allows the JVM to manage memory more flexibly than normal strong references.

Why it exists?

// Normal strong reference β€” object NEVER GC'd until reference is null
Person p = new Person(); // p holds strong reference
// GC cannot collect this β€” even if memory is low
Problem Solution via Reference
Caches holding objects forever SoftReference β€” JVM auto collects when memory is low
No way to tell JVM "collect if memory needed" WeakReference β€” JVM collects at next GC cycle
No way to know if object is already GC'd PhantomReference + ReferenceQueue β€” notifies after object removed
finalize() unreliable for cleanup PhantomReference + Cleaner API β€” reliable post-GC cleanup
Strong reference held unnecessarily Set reference to null or use WeakReference to allow GC
4 Types of References GC Collection Order
import java.lang.ref.*;

Person person = new Person("Alice");

// ─────────────────────────────────────
// 1. STRONG REFERENCE (default)
// ─────────────────────────────────────
// * Never GC'd until explicitly null
// * Normal Java reference
Person strong = new Person("Alice");  // strong reference
strong = null;  // now eligible for GC

// ─────────────────────────────────────
// 2. SOFT REFERENCE (Java 1.2)
// ─────────────────────────────────────
// * GC collects ONLY when JVM runs low on memory
// * Best for: CACHE (image cache, data cache)
SoftReference<Person> soft = new SoftReference<>(new Person("Alice"));

Person s = soft.get(); // returns object if not GC'd yet
if (s == null) {
    // GC already collected it β€” memory was low
    // reload / recreate object
}

// ─────────────────────────────────────
// 3. WEAK REFERENCE (Java 1.2)
// ─────────────────────────────────────
// * GC collects at NEXT GC cycle regardless of memory
// * Best for: WeakHashMap, listeners, canonicalized mappings
WeakReference<Person> weak = new WeakReference<>(new Person("Alice"));

Person w = weak.get(); // returns object if not yet GC'd
if (w == null) {
    // GC already collected it
}

// WeakHashMap β€” real use case
// key GC'd automatically when no strong reference to key exists
WeakHashMap<Person, String> map = new WeakHashMap<>();

// ─────────────────────────────────────
// 4. PHANTOM REFERENCE (Java 1.2)
// ─────────────────────────────────────
// * get() always returns NULL β€” cannot access object
// * Used to know EXACTLY when object is removed from memory
// * Best for: cleanup actions, resource management (Cleaner API)
ReferenceQueue<Person> queue = new ReferenceQueue<>();
PhantomReference<Person> phantom = new PhantomReference<>(new Person("Alice"), queue);

phantom.get(); // always returns null β€” by design

// when object GC'd β†’ phantom reference enqueued in ReferenceQueue
Reference<?> ref = queue.poll(); // poll to detect cleanup
if (ref != null) {
    // object fully removed from memory β€” safe to do cleanup
}
Memory pressure increases
         β”‚
         β–Ό
Strong Reference   β†’ ❌ NEVER collected (unless null)
         β”‚
         β–Ό
Soft Reference     β†’ βœ… collected when LOW MEMORY
         β”‚
         β–Ό
Weak Reference     β†’ βœ… collected at NEXT GC cycle
         β”‚
         β–Ό
Phantom Reference  β†’ βœ… collected AFTER finalization
                      enqueued in ReferenceQueue


Strong  β†’  I will NEVER let go
Soft    β†’  Let go only if REALLY needed
Weak    β†’  Let go at NEXT GC
Phantom β†’  Already gone β€” just notify me

Summary Table

Type GC Collects When get() returns Use Case
Strong Never (until null) Object always Default everywhere
Normal variables everywhere
Soft Low memory Object or null Image/data caching β€” SoftReference based cache
Weak Next GC cycle Object or null WeakHashMap, ThreadLocal internals`
Preventing memory leaks
Phantom After finalization Always null Cleanup, resource mgmt
Cleaner API (replaced finalize())

Object finalize() Method: The finalize() method is called by the garbage collector before an object is destroyed.

Purpose Important Notes
Perform cleanup operations (close files, release resources, database connections)
Last chance to perform actions before object is garbage collected
class Resource {
    @Override
    protected void finalize() throws Throwable {
        try {
            // Cleanup code
            System.out.println("Cleaning up resources");
            // Close connections, files, etc.
        } finally {
            super.finalize();
        }
    }
}
      
  • Deprecated since Java 9 - not recommended for new code
  • No guarantee when or if it will be called
  • Can cause performance issues
    Better alternatives:
  • Try-with-resources for AutoCloseable objects
  • close() methods
  • Cleaner API (Java 9+)
java// Modern approach - Try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // Use resource
} // Automatically closed, no finalize needed

public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
   // Shutdown the DataSource and its associated pool.
   @Override public void close() {
      //   ...
   }
}
// @since 1.5
public interface Closeable extends AutoCloseable {
  public void close() throws IOException;
}
      
The finalize method is unreliable and has been superseded by better resource management techniques in modern Java.

Factory Class: A Factory Class is a design pattern that provides a centralized way to create objects without exposing the creation logic to the client.


⚠️ **GitHub.com Fallback** ⚠️