Bytecode và javap — đọc instruction JVM
JVM là máy stack-based, không phải register-based như x86. Bytecode là tập opcode 1 byte JVM thực thi. javap dump bytecode để debug compile, hiểu lambda, decompile để verify optimize. 5 invoke* opcode và lý do invokedynamic là cách mạng.
Bạn viết:
int x = a + b * 2;
Compiler không sinh ra "máy lệnh CPU" — nó sinh ra bytecode, instruction của một máy ảo trừu tượng JVM. Cùng file .class chạy trên Linux x86, macOS arm64, Android Dalvik (đã chuyển), Windows — JVM mỗi platform translate bytecode → CPU instruction native lúc runtime.
Bytecode quan trọng vì:
- Là đơn vị compile Java — compiler frontend (javac) và optimizer backend (JIT) giao tiếp qua bytecode.
- Là boundary tương thích — Kotlin, Scala, Groovy, Clojure compile sang cùng bytecode → chạy chung JVM.
- Là tool debug ultimate: lambda gen ra gì,
String.formatthật sự gọi gì, switch expression compile thành table hay lookup —javapcho câu trả lời chính xác. - Là nền tảng hiểu JIT (bài kế tiếp). JIT đọc bytecode, profile, sinh native code optimize. Không hiểu bytecode → không debug được vì sao JIT không inline method bạn nghĩ là hot.
Bài này đi qua: stack-based VM (khác register-based), cấu trúc class file, constant pool, các opcode cơ bản, 5 invoke* opcode (invokevirtual / static / special / interface / dynamic), và case study lambda compile thành invokedynamic — feature design clever nhất của Java 7+.
1. Analogy — Máy tính bỏ túi RPN vs máy thường
Máy tính bỏ túi thường (Casio): bạn bấm 2 + 3 = để được 5. Trong CPU x86 cũng vậy — instruction có register: add eax, ebx (cộng eax với ebx, kết quả eax).
Máy tính RPN (Reverse Polish Notation, HP 12C, HP 50G): bạn nhập 2 ENTER 3 + để được 5. Mỗi số push vào stack. Operator pop 2 số trên cùng, push kết quả.
JVM là máy RPN. Mọi tính toán qua operand stack. Mọi instruction pop arg từ stack, push kết quả.
Java: int sum = a + b;
Bytecode: iload a -> push a vao stack
iload b -> push b vao stack
iadd -> pop 2 so, cong, push ket qua
istore sum -> pop ket qua, luu vao local sum
| Đời thường | JVM |
|---|---|
| Máy tính bỏ túi Casio | CPU x86/arm (register-based) |
| Máy tính RPN HP | JVM (stack-based) |
| Stack RPN | Operand stack |
| Phím số | iconst_*, bipush |
| Phím toán | iadd, imul, isub |
| Bộ nhớ tạm M+ | Local variable (istore/iload) |
JVM = máy RPN. Mọi instruction pop arg từ operand stack, push kết quả. Khác CPU x86 dùng register. Khi đọc bytecode, hình dung stack — mỗi dòng làm gì với stack top.
2. Class file — anatomy
Trước khi đọc bytecode, biết class file chứa gì.
.class file là binary, layout cố định (JVMS §4):
ClassFile {
u4 magic = 0xCAFEBABE // Marker JVM nhan dien
u2 minor_version
u2 major_version // 65 = Java 21, 61 = Java 17, 52 = Java 8
u2 constant_pool_count
cp_info constant_pool[count-1] // String, class name, method ref, ...
u2 access_flags // public, final, abstract, ...
u2 this_class
u2 super_class
u2 interfaces_count
u2 interfaces[count]
u2 fields_count
field_info fields[count]
u2 methods_count
method_info methods[count] // Bytecode trong day
u2 attributes_count
attribute_info attributes[count] // SourceFile, LineNumberTable, ...
}
Quan trọng nhất:
- Constant pool: bảng symbolic references — string literal, tên class, tên method, tên field. Bytecode tham chiếu các slot trong pool qua index 2-byte.
- methods: mỗi method có bytecode array, max stack depth, max local count.
Magic number 0xCAFEBABE
4 byte đầu mọi class file. Hex CAFE BABE — James Gosling chọn vì dễ nhớ và đọc được. JVM check magic — không match → reject "not a class file".
Major version
| Java | Major |
|---|---|
| 1.0 | 45 |
| 8 | 52 |
| 11 | 55 |
| 17 | 61 |
| 21 | 65 |
Class compile target Java 21 (major 65) chạy trên JRE 17 → UnsupportedClassVersionError. JRE chỉ chạy class major ≤ JRE version. Đây là lý do "compile target version phải ≤ runtime version".
javac --release 17 Foo.java → ép major = 61, đảm bảo chạy được JRE 17+.
3. javap — đọc bytecode
javap (Java disAssembler) ship với JDK. Dùng:
javap -c MyClass.class # Disassemble bytecode
javap -p -c MyClass.class # Include private member
javap -v MyClass.class # Verbose: constant pool + attributes
Ví dụ đơn giản
public class Hello {
public int add(int a, int b) {
int sum = a + b;
return sum;
}
}
Compile và disassemble:
javac Hello.java
javap -c Hello.class
Output:
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int add(int, int);
Code:
0: iload_1 // Push a (local slot 1) vao stack
1: iload_2 // Push b (local slot 2) vao stack
2: iadd // Pop 2 int, cong, push ket qua
3: istore_3 // Pop, luu vao local slot 3 (sum)
4: iload_3 // Push sum
5: ireturn // Return int top of stack
}
Đọc cột:
0:,1:,2:— bytecode offset (vị trí trong byte array).iload_1— opcode + operand._1là implicit operand (local slot 1).- Comment sau
//từ constant pool — javap resolve symbolic ref thành text.
Phân tích add method
a + b chỉ cần 3 instruction + return:
iload_1 # Push a
iload_2 # Push b
iadd # Pop 2 int -> push tong
ireturn # Pop -> return
Local slot:
- Slot 0:
this(instance method cóthisở slot 0). - Slot 1:
a(param 1). - Slot 2:
b(param 2). - Slot 3:
sum(local).
Tại sao iadd không có operand? — Vì JVM stack-based: opcode iadd ngầm định "pop 2 int trên cùng stack, push tổng". Không cần chỉ định argument.
So với x86:
mov eax, [a]
add eax, [b]
mov [sum], eax
ret
x86 cần chỉ rõ register eax. Bytecode không cần — stack ngầm định.
iconst_* và bipush
Push constant nhỏ vào stack:
iconst_m1 # Push -1
iconst_0 # Push 0
iconst_1 # Push 1
iconst_2 # Push 2
iconst_3 # Push 3
iconst_4 # Push 4
iconst_5 # Push 5
bipush 100 # Push byte (8-bit, -128 ~ 127)
sipush 1000 # Push short (16-bit, -32768 ~ 32767)
ldc #5 # Load constant tu pool (int 32-bit, float, String, ...)
Vì sao tách? Optimize size: iconst_0 1 byte, bipush 0 2 byte. Số dùng nhiều (-1 đến 5) có opcode riêng.
4. Operand stack và local variable
Mỗi method invocation tạo stack frame:
+---------------------+
| Operand Stack | <- max_stack (compute boi compiler)
+---------------------+
| Local Variables | <- max_locals
+---------------------+
| Frame Data | <- return PC, exception table reference, ...
+---------------------+
Frame nằm trên JVM Stack của thread (không phải heap). Method return → pop frame.
Stack tracing ví dụ
int compute(int x) {
int y = x * 2;
return y + 10;
}
Bytecode:
0: iload_1 # stack: [x]
1: iconst_2 # stack: [x, 2]
2: imul # stack: [x*2]
3: istore_2 # stack: [] local: [..., y=x*2]
4: iload_2 # stack: [y]
5: bipush 10 # stack: [y, 10]
7: iadd # stack: [y+10]
8: ireturn # return
Stack snapshot mỗi dòng — đọc tay theo dõi. Compiler tính trước max_stack = 2 (max depth tại bất kỳ điểm nào) và max_locals = 3 (this + x + y).
Verifier (mục bài 1) check: bytecode không ghi quá max_stack, không pop khi rỗng, không type mismatch (iadd cần 2 int trên top).
Type-prefix opcode
Opcode prefix biểu thị type:
i— intl— longf— floatd— doublea— reference (object, array)b— byte (chỉ trong load/store array)c— chars— short
Thao tác cộng: iadd, ladd, fadd, dadd. JVM không có generic add — type cố định để verifier check.
iload_1 # push int
lload_1 # push long (chiem 2 slot vi long 64-bit)
aload_1 # push reference
iadd # cong int
ladd # cong long
long và double chiếm 2 slot local (vì JVM slot 32-bit, 64-bit cần 2 slot). Slot 1 và 2 dùng cho long ở slot 1.
5. Constant pool
Mỗi class có constant pool — bảng symbolic reference. Bytecode tham chiếu pool qua index.
javap -v Hello.class
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Class #8 // Hello
#8 = Utf8 Hello
...
Loại entry phổ biến:
Utf8— string literal text (tên method, tên class, signature).Class— reference 1 class (chứa index Utf8 chứa tên).Methodref— reference 1 method (Class + NameAndType).NameAndType— name + descriptor pair (vd<init>:()V).String— string literal Java.Integer,Long,Float,Double— numeric constant.
Bytecode invokespecial #1 nghĩa "invoke method tại pool slot 1" — tức java/lang/Object.<init>:()V.
Method descriptor
Format compact biểu diễn signature:
(int, String) -> boolean => (ILjava/lang/String;)Z
() -> void => ()V
(byte[]) -> int => ([B)I
(Object, int[]) -> long[] => (Ljava/lang/Object;[I)[J
Quy tắc:
Bbyte,Sshort,Iint,Jlong,Ffloat,Ddouble,Cchar,Zboolean.Vvoid.L<class>;reference —Ljava/lang/String;.[<type>array —[Iint array,[[Iint 2D,[Ljava/lang/String;String array.
Format này từ JVMS §4.3.3 — format network/file binary cô đọng. Nhìn quen sau vài lần.
6. 5 invoke* opcode — cốt lõi method dispatch
JVM có 5 instruction để gọi method, mỗi cái cho kịch bản khác:
| Opcode | Dùng cho | Resolve khi nào |
|---|---|---|
invokestatic | Static method | Compile time, dispatch trực tiếp |
invokespecial | Constructor, super.x(), private | Compile time, dispatch trực tiếp (no override) |
invokevirtual | Instance method (non-private) | Runtime, dynamic dispatch theo type instance |
invokeinterface | Method declare ở interface | Runtime, lookup qua itable |
invokedynamic | Lambda, indy callsite | First call → compute target, cache cho lần sau |
invokestatic
class M {
public static int sum(int a, int b) { return a + b; }
}
M.sum(1, 2);
Bytecode:
0: iconst_1
1: iconst_2
2: invokestatic #2 // Method M.sum:(II)I
Đơn giản nhất. Không có this. Compile-time biết chính xác method nào — direct call.
invokespecial
class B extends A {
B() {
super(); // invokespecial A.<init>
}
void test() {
super.foo(); // invokespecial A.foo (no override lookup)
}
}
Dùng cho:
- Constructor (
<init>). super.method()— gọi method parent specifically, không lookup virtual.- Private method (Java < 11; Java 11+ dùng
invokevirtualvới check).
invokevirtual — dynamic dispatch
Đây là "polymorphism" của Java implement.
List<String> list = new ArrayList<>();
list.add("hello");
Bytecode:
aload_1
ldc "hello"
invokevirtual #5 // Method java/util/List.add:(Ljava/lang/Object;)Z
Static type là List — symbolic ref ghi List.add. Runtime, JVM tra vtable (virtual method table) của instance thực (ArrayList):
ArrayList vtable:
index 0: add(Object) -> ArrayList.add
index 1: get(int) -> ArrayList.get
...
invokevirtual lookup add(Object) trong vtable của ArrayList, gọi ArrayList.add. Đây là "late binding" / dynamic dispatch.
Cost: 1 vtable lookup mỗi call. Cache CPU + JIT inlining làm gần free trong hot loop.
invokeinterface
interface Drawable { void draw(); }
Drawable d = new Circle();
d.draw();
Bytecode:
aload_1
invokeinterface #5, 1 // InterfaceMethod Drawable.draw:()V
Khác invokevirtual: vtable interface không thẳng index. Vì 1 class implement nhiều interface, mỗi interface có method ở slot khác → JVM dùng itable (interface method table) — search nhanh.
Trước Java 8 invokeinterface chậm hơn invokevirtual ~10%. Modern JVM optimize bằng inline cache (itable lookup chỉ lần đầu, cache class result) → khác biệt không đáng kể.
invokedynamic — feature đột phá Java 7
invokedynamic (gọi tắt indy) khác hoàn toàn 4 cái trên: target method tự compute lúc runtime.
invokedynamic #5, 0 // BootstrapMethod #0
Cơ chế:
- Lần đầu chạy
invokedynamic— JVM gọi bootstrap method chỉ định trong constant pool (BootstrapMethods attribute). - Bootstrap method trả về
CallSitechứaMethodHandleđến target method thực sự. - JVM cache CallSite — lần sau gọi indy direct, không bootstrap lại.
Lần đầu chậm. Lần sau gần bằng invokestatic direct (JIT inline qua MethodHandle).
Tại sao quan trọng? Cho phép compiler emit code mà target chưa tồn tại lúc compile. Use case lớn nhất: lambda.
7. Case study — lambda compile thành gì?
List<Integer> nums = List.of(1, 2, 3);
nums.forEach(n -> System.out.println(n));
Pre-Java 8 compile cách "cũ" (vẫn hợp lệ): mỗi lambda → 1 anonymous inner class:
nums.forEach(new Consumer<Integer>() {
public void accept(Integer n) {
System.out.println(n);
}
});
Sinh Outer$1.class cho mỗi lambda. Bloat: 1000 lambda = 1000 file .class. Class load thêm 1000 lần. Slow startup.
Java 8 dùng invokedynamic — không sinh class trước:
javac Test.java
javap -c -p Test
Output (lược):
0: invokestatic #2 // Method java/util/List.of(...):... (tao list)
...
8: invokedynamic #4, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
13: invokeinterface #5, 2 // List.forEach
Dòng 8: invokedynamic produce object Consumer (cái bọc lambda). Bootstrap method LambdaMetafactory.metafactory (JDK chuẩn) sinh class lambda runtime, link với target body method (đã compile thành private static lambda$0 trong class chứa).
Lợi ích:
- Lazy class generation: lambda chưa dùng → không class. App startup nhanh.
- JIT optimize tốt: indy đã specialize qua metafactory, JIT inline lambda body vào caller.
- Ít class file: không sinh
Outer$1,Outer$2, ...
String concatenation Java 9+ cũng dùng indy (JEP 280):
String s = "Hello, " + name + "!";
Trước Java 9: javac sinh new StringBuilder().append(...).append(...).toString(). Java 9+: indy với bootstrap StringConcatFactory.makeConcatWithConstants — runtime sinh code optimize cho strategy phù hợp (vd biết length, alloc 1 lần).
8. Switch expression và bytecode
Switch trên int compile thành tableswitch hoặc lookupswitch:
int dayName(int d) {
switch (d) {
case 1: return 100;
case 2: return 200;
case 3: return 300;
default: return 0;
}
}
Bytecode:
tableswitch {
1: 28
2: 32
3: 36
default: 40
}
28: bipush 100 ireturn
32: sipush 200 ireturn
36: sipush 300 ireturn
40: iconst_0 ireturn
tableswitch: O(1) — index trực tiếp vào bảng theo (d - min). Compile thành tableswitch khi case dày đặc (1,2,3,4,5).
lookupswitch: case thưa (1, 100, 1000) → bảng (key, target) sort theo key, binary search O(log n).
So với chuỗi if/else (O(n) sequential), switch nhanh hơn nhiều với nhiều case.
Switch trên String (Java 7+) compile 2 tầng: tầng 1 hash → tableswitch theo hash; tầng 2 String.equals confirm (vì 2 string có thể cùng hash). Vẫn O(1) average.
Switch pattern matching (Java 21, JEP 441) compile thành invokedynamic với bootstrap SwitchBootstraps — runtime decide dispatch logic.
9. Pitfall tổng hợp
❌ Nhầm 1: Tưởng int chiếm 1 slot, long 1 slot.
JVM Long chiem 2 slot local. lstore_1 ghi long vao slot 1+2.
✅ Memorize: int/float/ref = 1 slot; long/double = 2 slot.
❌ Nhầm 2: Nghĩ bytecode chậm vì là VM.
// Bytecode chay tren JIT-compiled native code, sau warmup
// gan bang C performance.
✅ Bytecode là input cho JIT, không phải execution model thực sự.
❌ Nhầm 3: Tưởng tất cả method instance = invokevirtual.
Static -> invokestatic
Constructor / super / private (pre-11) -> invokespecial
Interface method -> invokeinterface
Instance non-private -> invokevirtual
Lambda / String concat / pattern switch -> invokedynamic
✅ Mỗi opcode có dispatch cost riêng. Hiểu để debug perf.
❌ Nhầm 4: Compile target version cao hơn JRE runtime.
javac --release 21 Foo.java # Class major 65
java -version # JRE 17 (major 61)
java Foo # UnsupportedClassVersionError
✅ Compile target ≤ JRE version. CI matrix test multiple version.
❌ Nhầm 5: Đọc bytecode mà bỏ qua constant pool.
javap -c chi disassemble bytecode + comment.
javap -v in ca constant pool, attribute, line table.
✅ Debug deep cần -v.
❌ Nhầm 6: Tưởng invokedynamic luôn chậm.
Indy cham lan dau (bootstrap). Lan sau JIT cache callsite, gan bang invokestatic.
✅ Đo qua JMH (bài Module 13) trước khi optimize.
10. 📚 Deep Dive Oracle
Spec / reference chính thức:
- JVMS §4 The class File Format — class file binary layout chi tiết.
- JVMS §6 The Java Virtual Machine Instruction Set — full opcode reference, mô tả từng instruction.
- JEP 309: Dynamic Class-File Constants —
CONSTANT_Dynamiccho indy nâng cao. - JEP 280: Indify String Concatenation — Java 9 đổi
+String sang indy. - JEP 441: Pattern Matching for switch — Java 21, pattern switch dùng indy.
- LambdaMetafactory javadoc — bootstrap method cho lambda indy.
javapman page — đầy đủ flag.
Ghi chú: JVMS §6 dày 200+ trang, mỗi opcode 1 entry. Không cần thuộc — biết tra. Khi debug bytecode lạ, copy opcode ra search trong §6 — có description chính xác (operand, stack effect, exception). JEP 280 và 441 minh hoạ pattern "đổi compile target từ class cụ thể sang indy" — design pattern đáng học cho ai viết compiler / DSL trên JVM.
11. Tóm tắt
- JVM là stack-based VM — instruction pop arg từ operand stack, push kết quả. Khác CPU x86/arm register-based.
- Class file binary, magic
0xCAFEBABE, major version chỉ JDK target. Major > runtime →UnsupportedClassVersionError. - Constant pool chứa symbolic reference (string, class name, method ref). Bytecode index vào pool.
- Stack frame mỗi method invocation: operand stack + local variables + frame data. Frame trên JVM Stack thread.
- Local slot: int/float/ref = 1 slot; long/double = 2 slot. Slot 0 thường là
thischo instance method. - Opcode type-prefix:
i*int,l*long,f*float,d*double,a*reference. Verifier check type strict. - 5 invoke*:
invokestatic— static, nothis, direct call.invokespecial— constructor, super, private (pre-11).invokevirtual— instance method, dynamic dispatch qua vtable.invokeinterface— interface method, dispatch qua itable.invokedynamic— bootstrap compute target lần đầu, cache lần sau.
- Lambda compile thành
invokedynamic+LambdaMetafactorybootstrap. Lazy gen class — startup nhanh hơn anonymous inner class. - String concat Java 9+ dùng
invokedynamic+StringConcatFactory. - Pattern switch Java 21 dùng
invokedynamic+SwitchBootstraps. - Switch trên int →
tableswitch(dense) hoặclookupswitch(sparse). O(1) hoặc O(log n). javap -cdump bytecode.javap -vthêm constant pool + attribute. Tool debug essential.- Bytecode → JIT → native code. Bytecode không chạy trực tiếp sau warmup — JIT specialize.
12. Tự kiểm tra
Q1Vì sao JVM thiết kế stack-based thay vì register-based như CPU x86?▸
3 lý do design:
- Portability: stack-based bytecode không assume số lượng register. Cùng bytecode chạy được trên CPU 8 register (x86 32-bit) hay 32 register (arm64). Register-based bytecode phải fix số register, không portable.
- Compact instruction:
iadd1 byte, không operand. Register-basedadd r1, r2, r34 byte (opcode + 3 register index). Class file nhỏ → load nhanh, network transfer rẻ. - Compiler đơn giản: compile expression tree sang stack-based natural — duyệt postorder, push left, push right, op. Register-based cần register allocation algorithm phức tạp.
Trade-off: stack-based interpret chậm hơn register-based ~30% (mỗi op cần push/pop). Nhưng JIT (bài kế tiếp) compile bytecode → native register-based code lúc runtime → bù toàn bộ overhead. Stack-based là interface intermediate, không phải execution final.
Dalvik (Android) chọn register-based bytecode vì target mobile — interpret tier vẫn quan trọng (battery, memory) → register-based nhanh hơn 30%. Trade-off khác.
Q25 invoke* opcode khác nhau thế nào, và compiler quyết định emit cái nào dựa trên gì?▸
invokestatic: gọi static method. Khôngthis. Compile-time biết chính xác method → direct call.invokespecial: gọi constructor (<init>),super.method(), private method (Java < 11). Bypass virtual lookup — gọi method exact của type compile-time.invokevirtual: gọi instance method non-private trên class. Runtime dispatch qua vtable — tìm override trong subclass.invokeinterface: gọi method declare ở interface. Runtime dispatch qua itable (interface method table) — phức tạp hơn vtable vì 1 class implement nhiều interface.invokedynamic: target method compute runtime qua bootstrap. Lambda, String concat (Java 9+), pattern switch (Java 21) đều dùng.
Compiler decide dựa trên:
- Method
static? →invokestatic. - Là constructor /
super.x/ private? →invokespecial. - Method declare ở interface? →
invokeinterface. - Lambda / String concat / pattern? →
invokedynamic. - Còn lại (instance method class) →
invokevirtual.
Java 11+ private method gọi qua invokevirtual (JEP 181) thay invokespecial — đơn giản hoá nest mate access.
Q3Đoạn sau compile ra bao nhiêu instruction? int x = 5; int y = x + 3; return y * 2;▸
int x = 5; int y = x + 3; return y * 2;Khoảng 8-9 instruction (chưa kể setup):
iconst_5 # push 5
istore_1 # x = 5 (slot 1)
iload_1 # push x
iconst_3 # push 3
iadd # pop 2, push x+3
istore_2 # y = ... (slot 2)
iload_2 # push y
iconst_2 # push 2
imul # pop 2, push y*2
ireturn # returnQuan sát:
- Mỗi thao tác Java thường = 1-2 bytecode. Code đơn giản → bytecode đơn giản.
iconst_*1 byte cho số 0-5. Số 100 sẽ làbipush 100(2 byte). Số 1000 sẽ làsipush 1000(3 byte). Số 100000 phảildc #N(load từ constant pool).- Local slot 0 nếu instance method =
this. Slot 1 =x, slot 2 =y.
JIT optimize: nhận thấy expression compile-time → có thể constant-fold thành return 16 (vì x = 5, y = 8, return 16) → bytecode chỉ bipush 16; ireturn. Nhưng bytecode emit từ javac thì verbose như trên — javac không constant-fold qua local variable.
Q4Lambda () -> System.out.println("hi") compile thành bytecode gì? Tại sao Java 8+ chọn invokedynamic thay vì anonymous class?▸
() -> System.out.println("hi") compile thành bytecode gì? Tại sao Java 8+ chọn invokedynamic thay vì anonymous class?2 phần được sinh:
- Method body: javac sinh private static method
lambda$Ntrong class chứa, body là code lambda:private static void lambda$0() { System.out.println("hi"); } - Indy callsite: tại nơi tạo lambda, javac emit
invokedynamic:invokedynamic #0:run:()Ljava/lang/Runnable; bootstrap: LambdaMetafactory.metafactory args: ..., reference to lambda$0, ...
Runtime: lần đầu chạy indy → bootstrap metafactory generate class YourClass$$Lambda$1 implement Runnable (qua InnerClassLambdaMetafactory — dùng ASM). Class này delegate run() sang lambda$0. Trả về instance qua CallSite.
Lần sau: indy callsite cache, gọi direct constructor không bootstrap lại.
Lý do chọn indy thay anonymous class:
- Lazy class gen: lambda chưa chạy → không class. Pre-Java 8 mỗi lambda 1 file
Outer$1.class→ app 1000 lambda + 1000 class load chậm startup. - Optimization headroom: indy tách "bootstrap strategy" khỏi bytecode → JDK update strategy không cần đổi class file user. Strategy hiện tại có thể đổi (vd cache instance lambda no-capture — singleton).
- JIT inline tốt: indy specialize callsite qua MethodHandle, JIT thấy direct chain → inline lambda body vào caller. Performance gần bằng inline manual.
- Future-proof: feature mới (record, pattern, valhalla) reuse indy infrastructure.
Đây là 1 trong design quyết định nhất Java 7-8 era — Brian Goetz và Mark Reinhold thiết kế để Java tránh "exponential class file bloat".
Q5Vì sao class compile target Java 21 không chạy được trên JRE 17?▸
Class file có field major_version 2 byte. JVM check:
if (class.major_version > supported_max_version) {
throw UnsupportedClassVersionError;
}JRE 21 hỗ trợ major ≤ 65 (Java 21). JRE 17 hỗ trợ major ≤ 61 (Java 17). Class compile target Java 21 có major = 65, vượt 61 → JRE 17 reject.
Lý do design strict: feature ngôn ngữ mới (vd switch pattern Java 21) emit bytecode opcode hoặc attribute mới. JVM cũ không hiểu → behavior undefined nếu cho chạy.
Backward compatible: JRE 21 chạy class major 45 (Java 1.0) bình thường. Forward không.
Cách compile cho target cũ:
javac --release 17 Foo.java # Major 61, dung API JDK 17
javac --target 17 --source 17 ... # Cu hon, can co --bootclasspath--release Java 9+ làm cả 2: ép major + ép API surface = JDK 17 (không cho dùng API mới hơn).
Production: CI matrix build với JDK 17 (release target), test trên JDK 17 + 21 — đảm bảo class chạy được nhiều version. Maven enforcer plugin check bytecode version.
Q6Vì sao constant pool tồn tại — tại sao không inline string/method ref trực tiếp vào bytecode?▸
3 lý do:
- Compact bytecode: bytecode tham chiếu pool qua index 2 byte.
invokevirtual #53 byte. Inline"java/util/List.add:(Ljava/lang/Object;)Z"= 40+ byte. Mỗi method gọi inline → bytecode bloat 10x. - String deduplication: cùng string xuất hiện 100 lần trong code → 1 entry pool, 100 reference 2-byte. Không inline → 100 copy.
- Symbolic resolution lazy: pool entry dạng symbolic (text "
java/util/List"). JVM resolve thành direct reference (con trỏ Class object) khi cần. Class chưa dùng → không resolve. Lazy loading như bài 1.
Hệ quả tinh tế: cùng class, JVM khác nhau (HotSpot vs OpenJ9 vs GraalVM) resolve cùng pool ra Class object khác nhau (mỗi JVM internal struct riêng). Bytecode portable, internal native code không.
Trade-off: indirection thêm 1 step (bytecode → pool index → resolved ref). JIT cache resolved ref trong inline cache → cost gần zero sau warmup.
Pool còn chứa BootstrapMethods attribute cho indy — bootstrap method tham chiếu qua pool index. Cấu trúc đồng nhất.
Bài tiếp theo: JIT compiler — interpreter, C1, C2, tiered compilation
Bài này có giúp bạn hiểu bản chất không?
Bình luận (0)
Đang tải...