Kiến trúc JVM & Runtime Data Areas
Hiểu kiến trúc JVM giúp bạn giải thích tại sao code Java chạy như vậy — từ StackOverflowError vs OutOfMemoryError, đến tại sao Java "khởi động chậm nhưng chạy nhanh". Bài này cung cấp bản đồ tổng thể trước khi đi sâu vào từng thành phần.
JVM là gì?
JVM (Java Virtual Machine) là máy ảo thực thi Java bytecode. Đây là lý do Java "Write Once, Run Anywhere" — code compile thành bytecode chung, JVM trên mỗi nền tảng (Windows, Linux, macOS) biết cách thực thi bytecode đó.
Quá trình từ .java đến thực thi
public class Main {
public static void main(String[] args) {
String greeting = "Hello, JVM!";
System.out.println(greeting);
}
}
Kiến trúc JVM — 3 thành phần chính
┌──────────────────────────────────────────────────────┐
│ JVM Architecture │
├──────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ ClassLoader │ Load .class files │
│ │ Subsystem │ (Bootstrap → Platform → App) │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Runtime Data Areas │ │
│ │ │ │
│ │ Per-Thread: Shared: │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ PC Register│ │ Heap │ │ │
│ │ ├──────────┤ ├──────────────┤ │ │
│ │ │ JVM Stack │ │ Method Area │ │ │
│ │ ├──────────┤ │(Metaspace) │ │ │
│ │ │Native Meth│ └──────────────┘ │ │
│ │ │ Stack │ │ │
│ │ └──────────┘ │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Execution │ Interpreter + JIT Compiler │
│ │ Engine │ + Garbage Collector │
│ └──────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
1. ClassLoader Subsystem
ClassLoader chịu trách nhiệm tìm và load .class files vào JVM:
// Khi bạn viết:
import java.util.ArrayList;
List<String> list = new ArrayList<>();
// ClassLoader tìm và load:
// 1. Bootstrap CL: java.util.ArrayList (core Java)
// 2. Application CL: your classes (classpath)
Chi tiết ClassLoader sẽ được trình bày ở bài ClassLoader & JIT.
2. Runtime Data Areas — 5 vùng memory
Đây là phần quan trọng nhất — nơi mọi dữ liệu chương trình tồn tại:
Per-Thread (Mỗi thread có riêng)
| Vùng | Mục đích | Lỗi liên quan |
|---|---|---|
| PC Register | Địa chỉ bytecode instruction đang thực thi | — |
| JVM Stack | Stack frames cho mỗi method call | StackOverflowError |
| Native Method Stack | Cho native methods (JNI — gọi C/C++) | StackOverflowError |
Shared (Tất cả threads dùng chung)
| Vùng | Mục đích | Lỗi liên quan |
|---|---|---|
| Heap | Tất cả objects và arrays | OutOfMemoryError: Java heap space |
| Method Area | Class metadata, static fields, constant pool | OutOfMemoryError: Metaspace |
3. Execution Engine
Execution Engine thực thi bytecode:
| Component | Vai trò |
|---|---|
| Interpreter | Đọc bytecode → thực thi từng instruction (chậm nhưng bắt đầu ngay) |
| JIT Compiler | Compile hot methods → native code (nhanh sau warmup) |
| Garbage Collector | Tự động dọn dẹp objects không còn sử dụng trên Heap |
PC Register (Program Counter)
Mỗi thread có PC Register riêng — lưu địa chỉ của bytecode instruction đang thực thi.
public void example() {
int a = 10; // PC → instruction #0
int b = 20; // PC → instruction #2
int sum = a + b; // PC → instruction #4
System.out.println(sum); // PC → instruction #7
}
Khi JVM chuyển giữa các threads (context switch), cần biết mỗi thread đang thực thi ở đâu để tiếp tục. PC Register lưu trữ vị trí này.
JVM Stack
Mỗi thread có 1 JVM Stack riêng, chứa các frames — mỗi method call tạo 1 frame mới:
public class StackDemo {
public static void main(String[] args) { // Frame 1: main
int result = calculate(10, 20); // Frame 2: calculate (push)
System.out.println(result); // Frame 2 popped, back to Frame 1
}
static int calculate(int a, int b) { // Frame 2
return add(a, b); // Frame 3: add (push)
}
static int add(int x, int y) { // Frame 3
return x + y; // Frame 3 popped
}
}
JVM Stack (Thread main):
│ Frame 3: add(x=10, y=20) │ ← Top (đang thực thi)
├────────────────────────────────┤
│ Frame 2: calculate(a=10, b=20)│
├────────────────────────────────┤
│ Frame 1: main(args=[]) │ ← Bottom
└────────────────────────────────┘
StackOverflowError
Khi stack đầy (quá nhiều frames) — thường do recursion không có base case:
// ❌ StackOverflowError — infinite recursion
public static int factorial(int n) {
return n * factorial(n - 1); // Không có base case!
// Mỗi call = 1 frame → stack đầy → StackOverflowError
}
// ✅ Có base case
public static int factorial(int n) {
if (n <= 1) return 1; // Base case — stop recursion
return n * factorial(n - 1);
}
Mặc định JVM stack size là 512KB–1MB (tuỳ JVM). Có thể điều chỉnh: java -Xss2m Main.
Heap
Heap là vùng memory lớn nhất, dùng chung bởi tất cả threads. Mọi objects và arrays đều nằm trên Heap:
public class HeapDemo {
// Instance field → trong object → trên Heap
private String name;
public void demo() {
// Object → Heap
HeapDemo obj = new HeapDemo(); // Object trên Heap
// Array → Heap
int[] numbers = new int[100]; // Array trên Heap
// String object → Heap (String Pool cũng trên Heap)
String text = new String("Hello"); // String object trên Heap
// Primitive local variable → Stack (KHÔNG phải Heap)
int count = 42; // Trên Stack frame
}
}
OutOfMemoryError: Java heap space
Khi Heap đầy và GC không thể giải phóng đủ memory:
// ❌ OutOfMemoryError — tạo quá nhiều objects
List<byte[]> leak = new ArrayList<>();
while (true) {
leak.add(new byte[1024 * 1024]); // 1MB mỗi iteration
// List giữ reference → GC không thể collect → Heap đầy
}
Heap size mặc định phụ thuộc vào JVM và hệ thống. Điều chỉnh:
-Xms256m: Initial heap size-Xmx1g: Maximum heap size
Method Area (Metaspace)
Method Area lưu trữ metadata của class — thông tin về class, không phải objects:
| Nội dung | Ví dụ |
|---|---|
| Class structure | Tên class, superclass, interfaces |
| Field info | Tên, type, access modifiers |
| Method info | Tên, parameters, bytecode |
| Constant Pool | String literals, numeric constants |
| Static fields | static int count = 0 |
public class User {
// Static field → Method Area
private static int totalUsers = 0;
// Instance field info → Method Area (metadata)
// Instance field value → Heap (trong object)
private String name;
// Method bytecode → Method Area
public String getName() {
return name;
}
}
Trước Java 8: Method Area = PermGen (fixed size, dễ bị OutOfMemoryError: PermGen space).
Từ Java 8: Method Area = Metaspace (sử dụng native memory, tự động mở rộng).
Điều chỉnh: -XX:MaxMetaspaceSize=256m
Ví dụ tổng hợp: Theo dõi Memory
public class MemoryDemo {
static int counter = 0; // Method Area (static field)
public static void main(String[] args) { // Frame trên JVM Stack
String name = "Java"; // Stack: reference, Heap: String Pool
User user = new User(name); // Stack: reference, Heap: User object
counter++; // Method Area: counter = 1
processUser(user); // New frame on Stack
}
static void processUser(User u) { // Frame mới trên Stack
String info = u.toString(); // Stack: reference, Heap: new String
System.out.println(info); // info eligible for GC after method returns
} // Frame popped, local vars gone
}
Method Area: Heap:
┌──────────────────┐ ┌──────────────────┐
│ MemoryDemo class │ │ User object │
│ counter = 1 │ │ name → "Java" │
│ main() bytecode │ ├──────────────────┤
│ processUser() │ │ "Java" (Pool) │
│ bytecode │ ├──────────────────┤
│ User class info │ │ String from │
└──────────────────┘ │ toString() │
└──────────────────┘
JVM Stack (main thread):
┌──────────────────┐
│ processUser() │
│ u → User@heap │
│ info → String@ │
├──────────────────┤
│ main() │
│ name → "Java"@ │
│ user → User@ │
└──────────────────┘
Tóm tắt
| Component | Vai trò | Scope |
|---|---|---|
| ClassLoader | Load .class files vào JVM | System |
| PC Register | Địa chỉ instruction đang thực thi | Per-thread |
| JVM Stack | Method call frames (local vars, operand stack) | Per-thread |
| Native Method Stack | Cho native methods (JNI) | Per-thread |
| Heap | Tất cả objects và arrays | Shared |
| Method Area | Class metadata, static fields, constant pool | Shared |
| Interpreter | Thực thi bytecode (chậm, start nhanh) | Execution Engine |
| JIT Compiler | Compile hot code → native (nhanh sau warmup) | Execution Engine |
| GC | Dọn dẹp objects không dùng trên Heap | Execution Engine |