Chapter 3. Compiling for the Java Virtual Machine
Chương này mô tả cách mã nguồn Java được biên dịch thành bytecode và cách JVM thực thi bytecode đó.
1️⃣ Quá trình biên dịch Java
Quá trình biên dịch trong Java diễn ra như sau:
1️⃣ Viết mã nguồn (.java
)
2️⃣ Biên dịch (javac
) → tạo bytecode (.class
)
3️⃣ JVM thực thi bytecode bằng Interpreter hoặc JIT Compiler
Ví dụ:
Mã trên sẽ được biên dịch thành bytecode, có thể xem bằng lệnh:
Kết quả bytecode (giản lược):
iconst_5
→ Đẩy giá trị5
lên Stackistore_1
→ Lưu vào biếnx
iload_1
,iload_2
→ Đọc giá trị củax
,y
iadd
→ Cộng hai sốinvokevirtual
→ Gọiprintln()
2️⃣ Tại sao dùng bytecode?
Độc lập nền tảng: JVM có thể chạy bytecode trên mọi hệ điều hành.
Tối ưu hóa: JIT Compiler dịch bytecode thành mã máy khi cần.
Bảo mật: JVM kiểm tra bytecode trước khi thực thi.
SÂU HƠN NỮA ĐI ANH!!! OK EM
Quá trình biên dịch mã nguồn Java thành bytecode 🚀
Quá trình biên dịch Java gồm nhiều bước để chuyển đổi mã nguồn (.java
) thành bytecode (.class
), sau đó JVM thực thi bytecode này. Chúng ta sẽ đi sâu vào từng giai đoạn.
1️⃣ Các giai đoạn của trình biên dịch Java (javac
)
javac
)Khi bạn chạy lệnh: `javac Example.java
trình biên dịch (javac
) thực hiện các bước sau:
🔹 Giai đoạn 1: Phân tích mã nguồn (Lexical Analysis & Parsing)
Trình biên dịch đọc mã nguồn và tách thành các token (từ khóa, toán tử, biến, phương thức, v.v.).
Lexical Analysis: Chia mã nguồn thành token (sử dụng bộ phân tích từ vựng - Lexer).
Parsing: Xây dựng cây cú pháp trừu tượng (AST - Abstract Syntax Tree) để kiểm tra cú pháp. Ví dụ:
Lexer sẽ tách thành:
Parser xây dựng cây AST:
🔹 Giai đoạn 2: Kiểm tra ngữ nghĩa (Semantic Analysis)
Kiểm tra kiểu dữ liệu (
Type Checking
): Đảm bảo biếnsum
có cùng kiểu dữ liệu vớia + b
.Kiểm tra phạm vi (
Scope Checking
): Kiểm traa, b
có được khai báo trước không.Kiểm tra tham chiếu: Kiểm tra
sum
có thể gán giá trị không.
Nếu có lỗi, trình biên dịch sẽ báo lỗi:error: cannot find symbol
🔹 Giai đoạn 3: Sinh mã trung gian (Intermediate Representation - IR)
Sau khi kiểm tra xong, trình biên dịch tạo một đại diện trung gian của chương trình, thường dưới dạng tập lệnh 3 địa chỉ (Three-Address Code - TAC) hoặc cây IR.
Ví dụ, mã Java:
có thể chuyển thành:
IR này giúp tối ưu hóa dễ dàng hơn trước khi tạo bytecode.
🔹 Giai đoạn 4: Sinh mã bytecode (Bytecode Generation)
Sau khi có IR, trình biên dịch chuyển đổi nó thành bytecode, là tập hợp các lệnh máy ảo cho JVM. Ví dụ:
Bytecode (javap -c Example.class
):
2️⃣ JVM xử lý bytecode như thế nào?
Khi chạy lệnh:java Example
JVM thực hiện 2 bước:
🔹 Bước 1: Kiểm tra mã bytecode (Bytecode Verification)
Trước khi chạy, JVM kiểm tra:
Không có lệnh bytecode bất hợp lệ.
Tất cả biến đều được khởi tạo trước khi sử dụng.
Không truy cập ngoài vùng nhớ.
Nếu phát hiện lỗi, JVM báo java.lang.VerifyError
.
🔹 Bước 2: Thực thi mã bytecode
Có 2 cách JVM thực thi bytecode:
🔸 Interpreter (Bộ thông dịch):
Đọc từng lệnh bytecode rồi thực thi ngay.
Nhanh trong khởi động nhưng chậm về lâu dài.
🔸 JIT Compiler (Just-In-Time Compilation)
Chuyển bytecode thành mã máy ngay khi cần.
HotSpot Compiler tối ưu mã hay dùng để tăng tốc.
Lưu mã đã biên dịch để dùng lại, giảm thời gian thực thi.
Ví dụ, nếu phương thức sum()
được gọi nhiều lần, JIT sẽ biên dịch trực tiếp sang mã máy để chạy nhanh hơn.
3️⃣ Kết luận
✔️ Biên dịch Java gồm: Lexing, Parsing, Semantic Analysis, IR Generation, Bytecode Generation. ✔️ Bytecode giúp Java độc lập nền tảng và dễ tối ưu hóa. ✔️ JVM thực thi bytecode bằng Interpreter và JIT Compiler để cân bằng tốc độ và hiệu suất.
Last updated