Understanding the Java Virtual Machine (JVM) — The Heart of Java

 

Understanding the Java Virtual Machine (JVM) — The Heart of Java


💡 1. Introduction

When you write and run a Java program, you rarely think about what happens underneath.
The Java Virtual Machine (JVM) is the engine that powers every Java application — translating your .class files into executable instructions for the host operating system.

Think of JVM as a bridge between your Java code and the machine hardware — ensuring platform independence, automatic memory management, and runtime optimizations.


🧩 2. The Java Execution Model

Let’s take a high-level look at what happens when you run a Java program:

Source Code (.java) ↓ Java Compiler (javac) ↓ Bytecode (.class files) ↓ JVM Class Loader → Bytecode Verifier ↓ Execution Engine (Interpreter / JIT) ↓ Operating System / CPU

So, your Java code never runs directly on your machine —
it runs inside the JVM, which interprets or compiles the bytecode for the host OS.


⚙️ 3. Step-by-Step: What Happens When You Run a Java Program

Let’s understand this with the command:

java HelloWorld

🔹 Step 1 — Class Loading

The Class Loader subsystem loads the HelloWorld.class file (bytecode) into memory.
It verifies dependencies, checks for security violations, and prepares the class for execution.

🔹 Step 2 — Bytecode Verification

The Bytecode Verifier ensures the code follows JVM rules —
no illegal memory access, stack overflows, or broken type constraints.

🔹 Step 3 — Memory Allocation

The Runtime Data Areas are created (Heap, Stack, Method Area, etc.) to hold class metadata, objects, and variables.

🔹 Step 4 — Execution

The Execution Engine starts executing bytecode instructions.
It uses an Interpreter or Just-In-Time (JIT) compiler for optimization.

🔹 Step 5 — Garbage Collection

When objects are no longer referenced, the Garbage Collector (GC) automatically reclaims their memory.


🧠 4. JVM Architecture Overview

Here’s a conceptual text diagram of the JVM components:

┌──────────────────────────────┐ │ Java Virtual Machine │ ├──────────────────────────────┤ │ Class Loader Subsystem │ ├──────────────────────────────┤ │ Runtime Data Areas │ │ ├─ Method Area │ │ ├─ Heap │ │ ├─ Java Stack │ │ ├─ PC Registers │ │ └─ Native Method Stack │ ├──────────────────────────────┤ │ Execution Engine │ │ ├─ Interpreter │ │ ├─ JIT Compiler │ │ └─ Garbage Collector │ ├──────────────────────────────┤ │ Native Interface (JNI) │ └──────────────────────────────┘

🧩 5. Class Loader Subsystem

The Class Loader is the first component that interacts with your .class files.

It works in three phases:

PhaseDescription
LoadingLoads class bytecode into JVM memory from disk, network, or JAR.
LinkingVerifies bytecode, allocates memory for static variables, and prepares constants.
InitializationRuns static blocks and initializes static fields.

There are three main class loaders:

Loader TypeDescription
Bootstrap ClassLoaderLoads core Java classes (java.lang, java.util, etc.) from JDK.
Extension ClassLoaderLoads JRE extension libraries from lib/ext.
Application ClassLoaderLoads classes from your project classpath.

🧮 6. JVM Memory Structure (Runtime Data Areas)

When a Java program starts, the JVM creates multiple memory regions to store different kinds of data.

Here’s the breakdown 👇

+----------------------------------+ | Method Area (shared) | | - Class definitions | | - Static variables | +----------------------------------+ | Heap | | - Objects and arrays | | - GC-managed memory | +----------------------------------+ | Java Stack | | - Method calls (Frames) | | - Local variables | | - Operand stacks | +----------------------------------+ | PC Registers | | - Current instruction pointer | +----------------------------------+ | Native Method Stack | | - For native C/C++ libraries | +----------------------------------+

🔹 1. Method Area

Stores:

  • Class structure (fields, methods)

  • Static variables

  • Method metadata

🔹 2. Heap

Stores:

  • All Java objects and arrays

  • Managed by Garbage Collector (GC)

🔹 3. Java Stack

Stores:

  • One stack per thread

  • Each frame contains local variables, intermediate results, and return values

🔹 4. Program Counter (PC) Register

  • Keeps track of the next instruction to execute.

🔹 5. Native Method Stack

  • Used when Java code calls native methods (e.g., via JNI to C/C++).


🚀 7. The Execution Engine

The Execution Engine is responsible for actually running the bytecode.

It has three main parts:

ComponentRole
InterpreterReads and executes bytecode instructions line by line.
JIT Compiler (Just-In-Time)Converts frequently executed bytecode into native machine code for faster performance.
Garbage CollectorAutomatically reclaims memory of unreferenced objects.

🔥 Just-In-Time (JIT) Compiler Process

Bytecode → Profiling → Native Code Compilation → Execution → Cache

When a method is called multiple times, JIT compiles it to native CPU code and stores it in memory for future use — improving performance drastically.


♻️ 8. Garbage Collection (GC)

💡 Why Needed?

In languages like C/C++, developers manually free memory.
In Java, GC automatically manages memory — cleaning up unreferenced objects.

🔹 Basic GC Algorithm

  1. Mark → Find all objects still referenced.

  2. Sweep → Remove unreferenced objects from memory.

  3. Compact → Rearrange memory to avoid fragmentation.

🔹 Generational GC Model

Heap is divided into:

  • Young Generation (Eden + Survivor spaces) → short-lived objects

  • Old Generation (Tenured) → long-lived objects

  • Permanent/Metaspace → class metadata

+------------------------+ | Young Gen (Eden + S0/S1) | +------------------------+ | Old Generation | +------------------------+ | Metaspace (JDK8+) | +------------------------+

⚙️ 9. JVM Lifecycle

1. Load class (.class file) 2. Verify bytecode 3. Allocate memory 4. Execute via Interpreter/JIT 5. Manage objects (GC) 6. Unload class when no longer needed

🧱 10. Key JVM Implementations

JVM TypeDescription
HotSpot (Oracle/OpenJDK)Default JVM, widely used in production.
GraalVMHigh-performance polyglot JVM supporting Java, Python, JS, etc.
OpenJ9 (IBM)Optimized for memory-constrained environments.
Dalvik / ARTJVM equivalents used in Android.

🧩 11. Example: Running a Program

Consider:

public class Demo { public static void main(String[] args) { int a = 10; int b = 20; int sum = a + b; System.out.println("Sum = " + sum); } }

Under the Hood:

  1. javac Demo.java → produces Demo.class (bytecode).

  2. java Demo → JVM loads Demo.class into memory.

  3. ClassLoader → loads and links.

  4. Execution Engine → interprets bytecode → CPU executes native instructions.

  5. Output printed → "Sum = 30".


🔍 12. Common JVM Interview Topics

ConceptExample Question
Class LoadingWhat are different class loaders in JVM?
Memory AreasExplain JVM memory structure.
GCWhat triggers garbage collection?
JITHow does JIT improve performance?
Stack vs HeapDifference between Stack and Heap memory.
OutOfMemoryErrorWhen does it occur and how to debug it?

🧠 13. Summary Table

ComponentResponsibility
Class LoaderLoads and links classes into memory
Method AreaStores class metadata, static variables
HeapStores objects and arrays
StackHolds method calls and local variables
PC RegisterTracks next bytecode instruction
Execution EngineRuns the bytecode instructions
JIT CompilerConverts hot bytecode to native code
GCFrees unused memory automatically

🧭 14. Key Takeaways

  • JVM = Abstraction Layer between Java and OS.

  • Provides Write Once, Run Anywhere capability.

  • Handles class loading, memory management, and optimization.

  • Garbage Collector ensures automatic memory cleanup.

  • JIT and HotSpot make modern JVMs extremely fast.


🧩 15. JVM Text-Based Architecture Summary

Java Source (.java) ↓ Compiler (javac) ↓ Bytecode (.class) ↓ +-----------------------------+ | Java Virtual Machine | |-----------------------------| | Class Loader | | Runtime Data Areas | | - Heap | | - Stack | | - Method Area | | Execution Engine | | - Interpreter | | - JIT Compiler | | - GC | +-----------------------------+ ↓ OS / CPU

Java Sorting Algorithms — Step-by-Step with Iterations

🔢 Java Sorting Algorithms — Step-by-Step with Iterations

Sorting is a fundamental concept in programming and data structures.
Below are the five main sorting algorithms, explained in simple terms with visual examples, loop-by-loop walkthroughs, and complete Java programs.


🧮 1️⃣ Bubble Sort — Iteration by Iteration

💡 Concept

Bubble Sort repeatedly compares adjacent elements and swaps them if they’re in the wrong order.
With each pass, the largest element “bubbles” to the end of the array.


⚙️ How It Works

  1. Start from the beginning of the array.

  2. Compare adjacent elements.

  3. Swap if the left is greater than the right.

  4. Repeat until no more swaps are needed.


🧩 Example: [5, 4, 1, 3]

Initial Array:
[5, 4, 1, 3]

Pass 1

ComparisonOperationResult
Compare 5 & 4Swap[4, 5, 1, 3]
Compare 5 & 1Swap[4, 1, 5, 3]
Compare 5 & 3Swap[4, 1, 3, 5]
✅ Largest element (5) “bubbled” to end.

Pass 2

ComparisonOperationResult
Compare 4 & 1Swap[1, 4, 3, 5]
Compare 4 & 3Swap[1, 3, 4, 5]
✅ Second largest (4) in place.

Pass 3

ComparisonOperationResult
Compare 1 & 3No swap[1, 3, 4, 5]
✅ Sorted.


💻 Java Code

package com.vi.sort; import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { int[] a = {5, 4, 1, 3}; System.out.println("Before Sorting: " + Arrays.toString(a)); doBubbleSort(a); System.out.println("After Sorting: " + Arrays.toString(a)); } public static void doBubbleSort(int[] a) { boolean sorted = false; int iteration = 1; while (!sorted) { sorted = true; System.out.println("Iteration " + iteration++ + ": " + Arrays.toString(a)); for (int i = 0; i < a.length - 1; i++) { if (a[i] > a[i + 1]) { int temp = a[i]; a[i] = a[i + 1]; a[i + 1] = temp; sorted = false; } } } } }

🧾 Output

Before Sorting: [5, 4, 1, 3] Iteration 1: [5, 4, 1, 3] Iteration 2: [4, 1, 3, 5] Iteration 3: [1, 3, 4, 5] After Sorting: [1, 3, 4, 5]

🧠 Key Points

  • Each pass “bubbles” the largest remaining element to the end.

  • Works well for small datasets.

  • Time: O(n²), Space: O(1), Stable: ✅ Yes


🧩 2️⃣ Insertion Sort — Iteration by Iteration

💡 Concept

Insertion Sort builds a sorted portion of the array one element at a time.
Each element is compared backward and inserted into its correct position.


⚙️ How It Works

  1. Start from index 1.

  2. Compare it to elements before it.

  3. Shift larger elements to the right.

  4. Insert current element into correct place.


🧩 Example: [5, 4, 1, 3]


Iteration 1 (i = 1, key = 4)

Left sorted part: [5]

Compare key=4 with previous elements:

  • 4 < 5 → shift 5 →
    [5, 5, 1, 3]

Insert 4 into the empty slot:

➡️ [4, 5, 1, 3]


Iteration 2 (i = 2, key = 1)

Left sorted part: [4, 5]

Compare key=1 with previous elements:

  • 1 < 5 → shift → [4, 5, 5, 3]

  • 1 < 4 → shift → [4, 4, 5, 3]

Insert 1:

➡️ [1, 4, 5, 3]


Iteration 3 (i = 3, key = 3)

Left sorted part: [1, 4, 5]

Compare key=3 with previous elements:

  • 3 < 5 → shift → [1, 4, 5, 5]

  • 3 < 4 → shift → [1, 4, 4, 5]

  • Next element is 1 (1 < 3) → stop early

Insert 3:

➡️ [1, 3, 4, 5]


💻 Java Code

package com.vi.sort; import java.util.Arrays; public class InsertionSort { public static void main(String[] args) { int[] a = {5, 4, 1, 3}; System.out.println("Before Sorting: " + Arrays.toString(a)); doInsertionSort(a); System.out.println("After Sorting: " + Arrays.toString(a)); } public static void doInsertionSort(int[] a) { for (int i = 1; i < a.length; i++) { int key = a[i]; int j = i - 1; while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; j--; } a[j + 1] = key; System.out.println("Iteration " + i + ": " + Arrays.toString(a)); } } }

🧾 Output

Iteration 1: [4, 5, 1, 3] Iteration 2: [1, 4, 5, 3] Iteration 3: [1, 3, 4, 5]

🧠 Key Points

  • Works best on nearly sorted arrays.

  • Time: O(n²), Space: O(1), Stable: ✅ Yes

  • Ideal for real-time insertion problems.


🧩 3️⃣ Selection Sort — Iteration by Iteration

💡 Concept

Selection Sort repeatedly finds the smallest element from the unsorted part and places it at the start.


⚙️ How It Works

  1. Loop through the array to find the smallest.

  2. Swap it with the first unsorted element.

  3. Move the sorted boundary forward.


🧩 Example: [5, 4, 1, 3]

PassSmallestSwapResult
111 ↔ 5[1, 4, 5, 3]
233 ↔ 4[1, 3, 5, 4]
344 ↔ 5[1, 3, 4, 5]
✅ Sorted [1, 3, 4, 5]



💻 Java Code

package com.vi.sort; import java.util.Arrays; public class SelectionSort { public static void main(String[] args) { int[] a = {5, 4, 1, 3}; System.out.println("Before Sorting: " + Arrays.toString(a)); doSelectionSort(a); System.out.println("After Sorting: " + Arrays.toString(a)); } public static void doSelectionSort(int[] a) { for (int i = 0; i < a.length - 1; i++) { int min = i; for (int j = i + 1; j < a.length; j++) { if (a[j] < a[min]) { min = j; } } int temp = a[min]; a[min] = a[i]; a[i] = temp; System.out.println("Iteration " + (i + 1) + ": " + Arrays.toString(a)); } } }

🧾 Output

Iteration 1: [1, 4, 5, 3] Iteration 2: [1, 3, 5, 4] Iteration 3: [1, 3, 4, 5]

🧠 Key Points

  • Simple but slow.

  • Time: O(n²), Space: O(1), Stable: ❌ No.

  • Best when minimizing swaps.


🧩 4️⃣ Merge Sort — Iteration by Iteration

💡 Concept

Merge Sort follows divide and conquer — splitting the array, sorting halves, and merging them.


⚙️ How It Works

  1. Divide the array into halves.

  2. Recursively sort each half.

  3. Merge the two sorted halves.


🧩 Example: [5, 4, 1, 3]

Divide → [5,4] [1,3] Sort → [4,5] [1,3] Merge → [1,3,4,5]

Merge process:
Left [4,5], Right [1,3]
1 < 4 → pick 1
3 < 4 → pick 3
Append remaining → [1,3,4,5]


💻 Java Code

package com.vi.sort; import java.util.Arrays; public class MergeSort { public static void main(String[] args) { int[] a = {5, 4, 1, 3}; System.out.println("Before Sorting: " + Arrays.toString(a)); mergeSort(a, 0, a.length - 1); System.out.println("After Sorting: " + Arrays.toString(a)); } public static void mergeSort(int[] a, int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(a, left, mid); mergeSort(a, mid + 1, right); merge(a, left, mid, right); } } public static void merge(int[] a, int left, int mid, int right) { int n1 = mid - left + 1, n2 = right - mid; int[] L = new int[n1]; int[] R = new int[n2]; for (int i = 0; i < n1; i++) L[i] = a[left + i]; for (int j = 0; j < n2; j++) R[j] = a[mid + 1 + j]; int i = 0, j = 0, k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) a[k++] = L[i++]; else a[k++] = R[j++]; } while (i < n1) a[k++] = L[i++]; while (j < n2) a[k++] = R[j++]; System.out.println("Merging step: " + Arrays.toString(a)); } }

🧾 Output

Before Sorting: [5, 4, 1, 3] Merging step: [4, 5, 1, 3] Merging step: [4, 5, 1, 3] Merging step: [1, 3, 4, 5] After Sorting: [1, 3, 4, 5]

🧠 Key Points

  • Time: O(n log n), Space: O(n)

  • Stable: ✅ Yes

  • Great for large datasets or linked lists.


🧩 5️⃣ Quick Sort — Iteration by Iteration

💡 Concept

Quick Sort also uses divide and conquer — but partitions around a pivot element.


⚙️ How It Works

  1. Choose a pivot.

  2. Partition the array around it.

  3. Recursively apply on left and right sides.


🧩 Example: [5, 4, 1, 3]

Step 1: Pivot = 3
→ Partition → [1, 3, 5, 4]
Step 2: Left [1] sorted, right [5,4] → Pivot = 4
→ Swap → [1, 3, 4, 5] ✅


💻 Java Code

package com.vi.sort; import java.util.Arrays; public class QuickSort { public static void main(String[] args) { int[] a = {5, 4, 1, 3}; System.out.println("Before Sorting: " + Arrays.toString(a)); quickSort(a, 0, a.length - 1); System.out.println("After Sorting: " + Arrays.toString(a)); } public static void quickSort(int[] a, int low, int high) { if (low < high) { int pi = partition(a, low, high); System.out.println("After partition (pivot index " + pi + "): " + Arrays.toString(a)); quickSort(a, low, pi - 1); quickSort(a, pi + 1, high); } } public static int partition(int[] a, int low, int high) { int pivot = a[high]; int i = low - 1; for (int j = low; j < high; j++) { if (a[j] < pivot) { i++; int temp = a[i]; a[i] = a[j]; a[j] = temp; } } int temp = a[i + 1]; a[i + 1] = a[high]; a[high] = temp; return i + 1; } }

🧾 Output

Before Sorting: [5, 4, 1, 3] After partition (pivot index 1): [1, 3, 5, 4] After partition (pivot index 3): [1, 3, 4, 5] After Sorting: [1, 3, 4, 5]

🧠 Key Points

  • Average: O(n log n), Worst: O(n²)

  • Space: O(log n)

  • Stable: ❌ No

  • Used internally in Arrays.sort() for primitives.


📊 Sorting Algorithm Summary Table

AlgorithmTime ComplexitySpace    StableBest Use
Bubble SortO(n²)O(1)            Educational, small datasets
Insertion SortO(n²)O(1)            Small or nearly sorted data
Selection SortO(n²)O(1)            Minimal swaps
Merge SortO(n log n)O(n)                Large or linked data
Quick SortO(n log n) avgO(log n)            Fast general-purpose sorting

🧭 Final Thoughts

  • 🧮 Bubble Sort → Great for beginners.

  • ✏️ Insertion Sort → Efficient for small, sorted data.

  • 🧲 Selection Sort → Minimal swaps, easy to implement.

  • ⚙️ Merge Sort → Predictable performance, always O(n log n).

  • Quick Sort → Industry favorite; excellent average speed.


Java ArrayList object creation best practice

Sometimes we can get java.lang.UnsupportedOperationException due to the bad coding practice while using Array. We can create ArrayList in multiple ways, here one thing we need to remind as new ArrayList<>() there is no fixed size and Arrays.asList(“test”) has the fixed size. Whenever we creates arrayList using Arrays.asList and appending another values will get ava.lang.UnsupportedOperationException.

Example here

package vinodvino;

 

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

 

public class ArrayTest {

 

public static void main(String args[]) {

    

List<String> firstArray=new ArrayList<>();

firstArray.add("Vinod");

firstArray.add("Shaji");

    System.out.println("FirstArrayValues"+firstArray);

 

 

List<String> secondArray=Arrays.asList("Vinod");

    secondArray.add("Shaji");

    System.out.println("SecondArrayValues"+secondArray);

 

 

}

}

 

Output

FirstArrayValues[Vinod, Shaji]
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at vinodvino.ArrayTest.main(ArrayTest.java:19)

 

In order to avoid this we can use array as 

 

List<String> secondArray=new ArrayList<>(Arrays.asList("Vinod"));

 

Confusion Matrix + Precision/Recall (Super Simple, With Examples)

  Confusion Matrix + Precision/Recall (Super Simple, With Examples) 1) Binary Classification Setup Binary classification means the model p...

Featured Posts