Chapter 6. The Java Virtual Machine Instruction Set


Chương 6 của Java Virtual Machine Specification (JVM Spec) SE 21 mô tả bộ thực thi của JVM (Java Virtual Machine Execution Engine), trong đó bao gồm cách JVM thực thi mã Java thông qua bytecode interpretation, Just-In-Time (JIT) compilation, và các cơ chế tối ưu hóa.

🔥 1. JVM Execution Engine là gì?

JVM Execution Engine là thành phần của JVM chịu trách nhiệm thực thi bytecode của chương trình Java. Nó có thể thực thi bằng cách thông dịch (interpretation) hoặc biên dịch Just-In-Time (JIT compilation).

👉 Quy trình từ mã Java đến thực thi: 1️⃣ Viết mã Java 2️⃣ Biên dịch sang bytecode (.class) bằng javac 3️⃣ Class Loader tải bytecode vào JVM 4️⃣ Execution Engine thực thi bytecode

  • 🏃 Interpreter (Thông dịch từng lệnh một)

  • 🚀 JIT Compiler (Tối ưu hóa và biên dịch sang mã máy)

🔹 2. Interpreter - Thông dịch Bytecode 🏃

  • Khi JVM chạy chương trình Java, nó không chạy trực tiếp mã Java, mà thực thi bytecode thông qua Interpreter.

  • Interpreter đọc từng lệnh bytecode và thực thi ngay lập tức mà không cần biên dịch trước.

  • Ưu điểm: Chạy ngay lập tức, không cần thời gian biên dịch.

  • Nhược điểm: Chậm vì phải thông dịch từng lệnh mỗi lần chạy.

📌 Ví dụ: Một chương trình Java đơn giản

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, JVM!");
    }
}

🔹 Dịch thành bytecode (javap -c HelloWorld.class)

0: getstatic     #2 // Lấy System.out
3: ldc           #3 // Load chuỗi "Hello, JVM!"
5: invokevirtual #4 // Gọi phương thức println
8: return

💡 Interpreter sẽ chạy từng lệnh trên và thực thi ngay lập tức.

🚀 3. JIT Compiler - Tăng tốc bằng Just-In-Time Compilation

💡 Vì Interpreter quá chậm, JVM dùng JIT Compiler để biên dịch bytecode sang mã máy thực thi trực tiếp.

🔸 JIT hoạt động như thế nào?

1️⃣ JVM chạy Interpreter trước. 2️⃣ Khi thấy một đoạn code được chạy nhiều lần (hot code), JIT sẽ biên dịch sang mã máy (native code). 3️⃣ JVM thay thế đoạn bytecode đó bằng mã máy đã biên dịch, giúp chạy nhanh hơn.

📌 Ví dụ:

public class LoopTest {
    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000; i++) {
            doSomething();
        }
    }
    static void doSomething() {}
}

💡 JIT Compiler sẽ thấy doSomething() được gọi rất nhiều lần (hot method), nên sẽ biên dịch sang mã máy thay vì tiếp tục thông dịch.

🔥 4. JIT Compiler có những loại nào?

JVM sử dụng nhiều kỹ thuật tối ưu hóa, bao gồm JIT Compilation. Có hai loại JIT chính:

1. C1 Compiler (Client Compiler - Biên dịch nhanh, tối ưu ít)

  • Dùng cho chương trình chạy nhanh ngay lập tức.

  • Ít tối ưu hóa hơn C2 nhưng khởi động nhanh.

2. C2 Compiler (Server Compiler - Biên dịch chậm, tối ưu mạnh)

  • Dùng cho chương trình chạy lâu dài, tối ưu mạnh mẽ.

  • Biên dịch nhiều hơn C1, loại bỏ code dư thừa.

📌 Bạn có thể bật JIT Logging để xem:

java -XX:+PrintCompilation LoopTest

🔹 Ví dụ output:

    45   1       LoopTest::doSomething (5 bytes)

💡 JVM đã biên dịch doSomething() sang mã máy!

🎯 5. Cơ chế tối ưu hóa của JIT Compiler

JIT không chỉ biên dịch, mà còn có nhiều tối ưu hóa thông minh:

🔸 (1) Method Inlining (Nhúng phương thức)

Thay vì gọi phương thức, JIT chèn trực tiếp nội dung phương thức vào, giúp tránh chi phí gọi hàm.

📌 Trước JIT Optimization

void foo() { bar(); }
void bar() { System.out.println("Hello"); }

📌 Sau JIT Optimization (Inlining)

void foo() { System.out.println("Hello"); }

🔸 (2) Loop Unrolling (Cuộn vòng lặp)

JIT có thể tăng tốc vòng lặp bằng cách mở rộng số lần lặp.

📌 Trước tối ưu hóa:

for (int i = 0; i < 4; i++) {
    doSomething();
}

📌 Sau tối ưu hóa:

doSomething();
doSomething();
doSomething();
doSomething();

💡 Giảm chi phí kiểm tra điều kiện vòng lặp.

🔸 (3) Dead Code Elimination (Loại bỏ code dư thừa)

Nếu JIT phát hiện một đoạn mã không bao giờ chạy, nó sẽ loại bỏ nó.

📌 Ví dụ:

if (false) {
    System.out.println("Không bao giờ chạy!");
}

💡 JIT sẽ xóa đoạn code trên.

🔥 6. Garbage Collection & Execution Engine

Execution Engine kết hợp với Garbage Collector để tối ưu hiệu suất.

  • GC sẽ thu hồi bộ nhớ của các đối tượng không còn tham chiếu.

  • JVM sử dụng nhiều thuật toán GC: G1 GC, ZGC, Shenandoah, CMS, Parallel GC.

🚀 7. Native Code Execution & JVM Tuning

Bạn có thể kiểm tra code đã được JIT biên dịch bằng:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopTest

💡 Điều này sẽ hiển thị mã máy thực sự chạy trên CPU.

🎯 8. So sánh Interpreter vs JIT Compiler

Đặc điểm
Interpreter
JIT Compiler

Tốc độ

Chậm hơn

Nhanh hơn

Cách hoạt động

Thông dịch từng lệnh bytecode

Biên dịch bytecode thành mã máy

Thời điểm thực thi

Ngay lập tức

Sau một số lần chạy

Tối ưu hóa

Không có

Có nhiều tối ưu hóa

Ứng dụng

Dùng khi chạy code lần đầu

Dùng cho code chạy thường xuyên

🎯 9. Tổng kết

1️⃣ JVM Execution Engine là nơi bytecode được thực thi. 2️⃣ Ban đầu, JVM dùng Interpreter để chạy bytecode. 3️⃣ Nếu thấy một đoạn code chạy nhiều lần, JVM dùng JIT Compiler để tối ưu. 4️⃣ JIT thực hiện nhiều tối ưu hóa như Inlining, Loop Unrolling, Dead Code Elimination. 5️⃣ JVM kết hợp với Garbage Collector để quản lý bộ nhớ hiệu quả.

Last updated