Backend

99 Ngày Spring — Ngày 88: GraalVM Native Image

SSite Admin
23 tháng 10, 2026 3 phút đọc 5 lượt xem
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.

GraalVM Native Image

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-image

3. 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/

S

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.

Bài viết liên quan

99 Ngày Spring — Ngày 89: Docker hóa Spring Boot

Dockerfile nhiều stage, layered jar để tận dụng cache, chạy không dùng root và buildpacks khi không muốn viết Dockerfile.

23 thg 10, 20263 phút7
99 Ngày Java — Ngày 89: Mockito

mock, stub và verify, @Mock với @InjectMocks, ArgumentCaptor và những gì không nên mock.

23 thg 10, 20263 phút6
99 Ngày Java — Ngày 88: Unit Test với JUnit 5

@Test và vòng đời, các assertion quan trọng, parameterized test và cách viết test đọc được như đặc tả.

23 thg 10, 20263 phút5