Class loading, bytecode và JPMS
Hiểu vòng đời class giúp giải thích lỗi linkage, static initialization, plugin architecture, memory leak do class loader và ranh giới module. Đây là phần nối giữa mã Java bạn viết và cách JVM thực sự nạp, liên kết, khởi tạo và tổ chức code khi chạy.
1. Class file và bytecode
File .class chứa magic number, version, constant pool, metadata của field/method, attributes và bytecode của method. Constant pool lưu literal cùng symbolic reference; JVM resolve symbolic reference thành runtime reference trong quá trình linking hoặc khi reference được dùng lần đầu. Bytecode là tập instruction theo mô hình operand stack, không phải mã máy của CPU.
javac PaymentService.java
javap -c -p -v PaymentService.class
javap cho thấy descriptor sau type erasure, synthetic/bridge method, constant pool và instruction. Đây là công cụ tốt để kiểm chứng thay vì đoán compiler đã tạo gì.
2. Loading, linking và initialization
Class.<clinit>.| Giai đoạn | Việc xảy ra |
|---|---|
| Loading | Tìm binary representation và tạo đối tượng Class. |
| Verification | Kiểm tra class file hợp lệ và bytecode an toàn theo constraint của JVM. |
| Preparation | Cấp storage cho static field và gán default value. |
| Resolution | Chuyển symbolic reference thành direct/runtime reference; có thể lazy. |
| Initialization | Chạy class initialization method <clinit>, gồm static initializer và static field initializer. |
Initialization được kích hoạt bởi “active use” như tạo instance, gọi static method, ghi hoặc đọc static field không phải compile-time constant. Chỉ load class chưa nhất thiết chạy static block. JVM đồng bộ initialization để một class không được initialize đồng thời nhiều lần.
ClassNotFoundException thường đến từ API load theo tên; NoClassDefFoundError là JVM đã cần class nhưng definition không còn khả dụng hoặc initialization trước đó thất bại; NoSuchMethodError thường báo binary incompatibility giữa compile-time và runtime dependency.3. Class loader và delegation
Class identity là cặp binary name + defining class loader. Hai class cùng tên do hai loader khác nhau định nghĩa không phải cùng runtime type. Built-in loaders gồm bootstrap, platform và application loader. Mô hình parent delegation thường hỏi parent trước, giúp core classes không bị tùy tiện thay thế.
Plugin/container có thể dùng child-first hoặc loader hierarchy riêng để cô lập dependency. Khi unload plugin, mọi reference đến class, instance và defining loader phải biến mất. Thread context class loader, static cache, driver registry hoặc ThreadLocal có thể giữ loader và gây metaspace leak.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> pluginType = Class.forName("demo.Plugin", true, loader);
4. Java Platform Module System (JPMS)
JPMS tạo explicit module graph. Module khai báo dependency và package được export; strong encapsulation giúp phân biệt public cho nội bộ module với public API cho module khác.
module com.example.orders {
requires java.net.http;
exports com.example.orders.api;
uses com.example.orders.spi.PricingProvider;
}
| Directive | Ý nghĩa |
|---|---|
requires | Đọc module khác. |
exports | Cho module khác compile/access public types của package. |
opens | Cho deep reflection tại runtime; không giống exports. |
uses / provides ... with | Khai báo consumer/provider cho ServiceLoader. |
requires transitive | Consumer của module hiện tại cũng đọc dependency đó. |
Unnamed module giữ compatibility cho classpath. Automatic module giúp migration nhưng module name có thể thiếu ổn định. Split package bị hạn chế trong module graph. Với framework reflection, ưu tiên opens package to framework.module thay vì mở toàn module.
exports là API access ở mức public type; opens phục vụ deep reflection runtime. Một package có thể cần một trong hai hoặc cả hai tùy cách framework truy cập.5. ServiceLoader và runtime image
ServiceLoader<PricingProvider> providers = ServiceLoader.load(PricingProvider.class);
PricingProvider provider = providers.findFirst().orElseThrow();
ServiceLoader hỗ trợ plugin theo contract mà consumer không phụ thuộc implementation cụ thể.
jdeps --print-module-deps app.jar
jlink --add-modules com.example.app --output runtime
jdeps phân tích module dependency; jlink tạo runtime image chỉ chứa module cần thiết. Lợi ích là image nhỏ và kiểm soát runtime tốt hơn, đổi lại build/deployment phức tạp hơn.
uses → provider khai báo provides ... with → ServiceLoader tìm implementation → jdeps/jlink giúp kiểm soát dependency và runtime image.6. Checklist hiểu sâu
- Phân biệt load, link và initialize.
- Giải thích vì sao static initialization có thể deadlock hoặc làm class unusable.
- Chẩn đoán
NoClassDefFoundError/NoSuchMethodErrorbằng dependency runtime. - Giải thích class identity có class loader.
- Phân biệt
exportsvàopens. - Dùng
ServiceLoadermà không hard-code provider.
Tự kiểm tra tốt nhất bằng cách giải thích từng mục bằng ví dụ thực tế, sau đó dùng javap, dependency tree/runtime image hoặc class-loader logging để chứng minh thay vì chỉ nhớ định nghĩa.