99 Ngày Spring — Ngày 88: GraalVM Native Image
Một ứng dụng Spring Boot trên JVM mất vài giây để khởi động và cần thời gian cho JIT làm nóng. GraalVM native image biên dịch trước toàn bộ ứng dụng thành một file thực thi: khởi động trong vài chục mili giây, dùng ít bộ nhớ hơn. Cái giá là build lâu và một loạt ràng buộc với những gì Java thường làm lúc chạy.

1. Thế giới đóng và AOT processing
Native image phân tích toàn bộ code từ điểm vào và chỉ giữ lại phần với tới được; mọi thứ phải biết lúc build. Spring thì vốn quyết định nhiều thứ lúc chạy: quét classpath, tạo proxy, đọc annotation bằng reflection. Bước AOT của Spring Boot chạy lúc build để chuyển các quyết định đó thành code Java được sinh sẵn cùng các file hint cho GraalVM. Hệ quả là tập bean bị cố định: @Profile và @ConditionalOnProperty được đánh giá lúc build, không phải lúc chạy.
Giá trị cấu hình vẫn đọc lúc chạy; chỉ cấu trúc bean là cố định.
Không nạp được lớp mới lúc chạy; không có JIT.
2. Build native executable
Dự án tạo từ Spring Initializr với GraalVM Native Support đã có sẵn plugin native-maven-plugin và profile native. Có hai đường: biên dịch trực tiếp trên máy đã cài GraalVM, hoặc dùng buildpacks để tạo container image chứa file native mà không cần cài GraalVM. Build tốn vài phút và nhiều gigabyte RAM, nên thường chỉ chạy trên CI.
# native executable on a machine with GraalVM for JDK 21
./mvnw -Pnative native:compile
./target/orders
# container image through buildpacks (needs Docker only)
./mvnw -Pnative spring-boot:build-image3. Reflection, proxy và resource cần hint
Lớp chỉ được truy cập qua reflection sẽ bị loại khỏi file native nếu không có hint. Spring tự sinh hint cho bean, entity và các kiểu trong chữ ký controller. Phần còn lại bạn phải khai: kiểu được serialize gián tiếp, tài nguyên đọc bằng tên, thư viện chưa hỗ trợ native. Triệu chứng điển hình là chạy tốt trên JVM nhưng lỗi ClassNotFoundException hoặc JSON rỗng trên bản native.
@Configuration
@RegisterReflectionForBinding(PartnerQuote.class) // serialized via reflection
@ImportRuntimeHints(AppHints.class)
class NativeConfig {}
class AppHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader loader) {
hints.resources().registerPattern("templates/*.html");
}
}4. Khi nào đáng dùng
Native image có lợi rõ khi thời gian khởi động và bộ nhớ là chi phí thật: hàm serverless, công cụ dòng lệnh, service scale về không. Với service chạy dài ngày và tải ổn định, JVM cùng JIT thường cho thông lượng đỉnh cao hơn. Hãy cân nhắc cả chi phí vận hành: pipeline chậm hơn, và phải test trên chính bản native.
Chạy test ở chế độ native trên CI (./mvnw -PnativeTest test) trước khi tin bản build.
Kiểm tra thư viện trong danh sách hỗ trợ native trước khi cam kết.
Thử trước các lựa chọn rẻ hơn: Class Data Sharing và virtual thread trên JVM.
Native image đổi sự linh động lúc chạy lấy khởi động nhanh và bộ nhớ nhỏ. Ngày 89 quay về cách đóng gói phổ biến nhất: Docker hóa Spring Boot.
Tài liệu đối chiếu
https://docs.spring.io/spring-boot/reference/packaging/native-image/index.html
https://www.graalvm.org/latest/reference-manual/native-image/
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


