+3

JDK 25 - Kỷ nguyên mới của hiệu suất và sự tối giản

Vào ngày 16 tháng 9 năm 2025, thế giới Java đã chính thức đón nhận JDK 25 – phiên bản hỗ trợ dài hạn (LTS) quan trọng nhất trong những năm gần đây. Không chỉ dừng lại ở việc cập nhật định kỳ, JDK 25 là kết quả hội tụ của các dự án lớn như Loom (đa luồng), Amber (cú pháp), và Valhalla (hiệu suất bộ nhớ). Với 18 JEPs (Java Enhancement Proposals), bản phát hành này hứa hẹn thay đổi hoàn toàn cách chúng ta viết code: ngắn gọn hơn, chạy nhanh hơn và tốn ít RAM hơn.

1. Cải tiến Ngôn ngữ: Code Ngắn Gọn, Tư Duy Hiện Đại

Nhóm tính năng này giúp Java rũ bỏ sự rườm rà (boilerplate), giúp lập trình viên tập trung vào logic nghiệp vụ.

JEP 512: Compact Source Files & Instance Main Methods:

Xóa bỏ rào cản cho người mới. Bạn có thể chạy một file Java mà không cần khai báo lớp (class) hay từ khóa static cho hàm main.

// File: Hello.java
void main() {
    System.out.println("Chào mừng bạn đến với Java 25!");
}

JEP 511: Module Import Declarations

Thay vì hàng chục dòng import, bạn chỉ cần import module java.base;. Toàn bộ các gói như java.util, java.io, java.net sẽ sẵn sàng sử dụng.

import module java.base; // Tự động có List, Map, File, URL...

void main() {
    var list = List.of("A", "B");
    var path = Path.of("test.txt");
    System.out.println(list);
}

JEP 513: Flexible Constructor Bodies

Cho phép thực hiện logic (kiểm tra, tính toán) trước khi gọi super().

public class User {
    protected String name;
    public User(String name) { this.name = name; }
}

public class Admin extends User {
    public Admin(String name) {
        var trimmedName = name.trim(); // Tính toán trước
        if (trimmedName.isEmpty()) throw new IllegalArgumentException(); // Validate trước
        super(trimmedName); // Gọi lớp cha sau khi đã an tâm về dữ liệu
    }
}

JEP 507: Primitive Types in Patterns

Cho phép sử dụng kiểu nguyên thủy (int, double...) trong instanceof và switch, giúp xử lý dữ liệu thô đồng nhất như đối tượng.

void test(Object obj) {
    if (obj instanceof int i && i > 100) {
        System.out.println("Số nguyên lớn: " + i);
    }
}

2. Hiệu suất Hệ thống: Tối ưu RAM và CPU

Đây là phần "đáng tiền" nhất đối với các doanh nghiệp, đặc biệt là các doanh nghiệp đang chạy hệ thống lớn trên Cloud.

JEP 519: Compact Object Headers

  • Mỗi object trong Java giờ đây chiếm ít bộ nhớ hơn nhờ nén phần Header từ 128-bit xuống 64-bit.
  • Điều này có thể góp phần làm giảm tiêu thụ RAM từ 10-20% cho toàn bộ ứng dụng mà không cần sửa code.

JEP 521: Generational Shenandoah

  • Nâng cấp bộ dọn rác Shenandoah lên kiến trúc phân thế hệ, giờ đây việc dọn dẹp sẽ được phân chia đối tượng theo lứa tuổi. Các đối tượng "trẻ" (mới tạo) được dọn dẹp thường xuyên và nhanh hơn, giúp ứng dụng không bao giờ bị "khựng", giảm độ trễ xuống mức cực thấp..

JEP 502: Stable Values (Preview)

Giúp tạo ra các biến "gần như hằng số". Điều này cho phép định nghĩa các trường chỉ ghi một lần (write-once), giúp JIT Compiler tối ưu hóa mã máy mạnh mẽ như khi xử lý hằng số.

// JVM sẽ tối ưu hóa trường này như thể nó là 'final' sau khi được gán lần đầu
private static final StableValue<String> config = StableValue.newInstance();

JEP 514 & 515: AOT (Ahead-of-Time) Improvements

  • Tối ưu hóa quá trình biên dịch trước (AOT Ahead-of-Time) để ứng dụng Java khởi động nhanh như Go hay Rust, rất quan trọng cho Serverless/Lambda.

JEP 503: Remove the 32-bit x86 Port

  • Loại bỏ mã nguồn cũ kỹ để tập trung tối ưu 100% cho các chip 64-bit hiện đại.
  • Điều này có nghĩa là từ bây giờ Java sẽ chính thức ngừng hỗ trợ kiến trúc x86 32-bit. Điều này giúp đội ngũ phát triển tập trung tối ưu cho các kiến trúc 64-bit hiện đại hơn (x64 và AArch64).

3. Concurrency (Dự án Loom): Đa luồng Hiệu năng cao

JEP 506: Scoped Values (Chính thức)

  • Là sự thay thế hoàn hảo cho ThreadLocal
  • ScopedValue cho phép chia sẻ dữ liệu bất biến giữa các luồng một cách an toàn và cực kỳ tiết kiệm bộ nhớ khi dùng với Virtual Threads.

Vấn đề của cái cũ (ThreadLocal):

  1. Memory Leak (Rò rỉ bộ nhớ): ThreadLocal giữ dữ liệu cho đến khi luồng bị tiêu biến. Với Thread Pool (luồng được tái sử dụng), nếu bạn quên gọi .remove(), dữ liệu của người dùng A có thể bị lộ sang người dùng B.

  2. Khả năng thay đổi (Mutability): Bất kỳ phương thức nào cũng có thể gọi .set() để sửa dữ liệu trong ThreadLocal, gây khó khăn cho việc debug vì không biết ai đã đổi giá trị.

  3. Hiệu suất kém với Virtual Threads: Khi bạn có hàng triệu Virtual Threads, việc mỗi luồng mang theo một bản sao ThreadLocal nặng nề sẽ làm cạn kiệt bộ nhớ Heap.

👉️Giải pháp của Scoped Values: Scoped Values cho phép truyền dữ liệu đến các phương thức con mà không cần dùng tham số, nhưng dữ liệu này là bất biến (Immutable) và chỉ tồn tại trong một phạm vi (Scope) nhất định.

Ví dụ cụ thể: Truyền thông tin User qua các tầng Service

// 1. Khai báo ScopedValue
public final static ScopedValue<String> PRINCIPAL = ScopedValue.newInstance();

void handleRequest(String user) {
    // 2. Thiết lập phạm vi (Scope)
    // Sau khi block này kết thúc, PRINCIPAL tự động bị xóa, không lo Memory Leak
    ScopedValue.where(PRINCIPAL, user).run(() -> {
        serviceA();
    });
}

void serviceA() {
    serviceB();
}

void serviceB() {
    // 3. Đọc dữ liệu (Chỉ đọc, không thể sửa)
    System.out.println("User đang thực hiện: " + PRINCIPAL.get());
}

JEP 505: Structured Concurrency

Đúng như tên gọi nó cho phép chúng ta nhóm các luồng liên quan vào một đơn vị công việc. Nếu một luồng con thất bại, các luồng khác sẽ bị hủy ngay lập tức, tránh lãng phí tài nguyên.

Vấn đề của cái cũ (ExecutorService):

Khi bạn chia một tác vụ lớn thành nhiều tác vụ con (ví dụ: gọi API lấy thông tin User và gọi API lấy thông tin Order cùng lúc):

  1. Tác vụ mồ côi (Orphaned tasks): Nếu việc lấy User bị lỗi, việc lấy Order vẫn tiếp tục chạy lãng phí tài nguyên dù kết quả cuối cùng không còn dùng được.

  2. Khó gỡ lỗi (Observability): Thread Dump không cho thấy mối quan hệ cha-con giữa các luồng.

  3. Hủy bỏ phức tạp: Bạn phải tự tay quản lý việc interrupt() các luồng con khi có lỗi xảy ra.

👉️ Giải pháp của Structured Concurrency: Nó coi các luồng con là một phần của luồng cha. Nếu một luồng con thất bại, toàn bộ các luồng con khác trong nhóm đó sẽ bị hủy ngay lập tức (Fail-fast).

Ví dụ cụ thể: Tổng hợp dữ liệu từ 2 API khác nhau

public Response handleData() throws ExecutionException, InterruptedException {
    // 1. Mở một phạm vi cấu trúc (Scope)
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        
        // 2. Fork (tách) ra các luồng con
        Subtask<String> userTask = scope.fork(() -> fetchUser());
        Subtask<Integer> orderTask = scope.fork(() -> fetchOrder());

        // 3. Chờ tất cả hoàn thành hoặc một cái bị lỗi
        scope.join();           
        scope.throwIfFailed();  // Nếu fetchUser lỗi, fetchOrder sẽ bị hủy ngay lập tức

        // 4. Kết hợp kết quả
        return new Response(userTask.get(), orderTask.get());
    }
}

4. Bảo mật và Giám sát (JFR & Crypto)

JEP 510: Key Derivation Function (KDF) API

API mới để tạo khóa bảo mật.

KDF kdf = KDF.getInstance("HKDFWithHmacSHA256");
SecretKey derivedKey = kdf.deriveKey(...);

Mục đích chính của API này là tạo ra các khóa mật mã mạnh (Cryptographic Keys) từ các nguồn dữ liệu ban đầu không đủ độ an toàn hoặc không có định dạng chuẩn, ví dụ như:

  • Mật khẩu (Passwords): Biến mật khẩu ngắn của người dùng thành một khóa dài và phức tạp để mã hóa dữ liệu.

  • Bí mật chung (Shared Secrets): Sau khi thực hiện trao đổi khóa (như Diffie-Hellman), API này giúp trích xuất khóa đối xứng thực sự để bắt đầu mã hóa kênh truyền.

Điểm nổi bật so với cách cũ

  • Tính thống nhất: Thay vì mỗi thuật toán có một cách gọi khác nhau (như SecretKeyFactory cho PBKDF2), nay tất cả đều dùng chung class KDF.

  • Hỗ trợ các thuật toán hiện đại: Tập trung vào các chuẩn mới như HKDF (HMAC-based Extract-and-Expand KDF) — "trái tim" của giao thức TLS 1.3.

  • Tính linh hoạt (Extensibility): Cho phép các bên thứ ba (Security Providers) dễ dàng tích hợp các thuật toán mới mà không làm thay đổi cách viết code của lập trình viên.

Dưới đây là cách sử dụng API mới để tạo khóa bằng thuật toán HKDF:

import javax.crypto.KDF;
import javax.crypto.SecretKey;
import java.security.spec.KDFParameterSpec;

public void kdfExample() throws Exception {
    // 1. Khởi tạo KDF với thuật toán mong muốn (ví dụ HKDF)
    KDF kdf = KDF.getInstance("HKDFWithHmacSHA256");

    // 2. Thiết lập tham số (Salt, Input Key Material)
    // Giả sử input là một bí mật chung từ quá trình trao đổi khóa
    byte[] salt = "random_salt".getBytes();
    byte[] inputSecret = "shared_secret_from_dh".getBytes();
    
    // 3. Trích xuất khóa (Derive Key)
    // Bạn có thể chỉ định độ dài khóa mong muốn (ví dụ 32 bytes cho AES-256)
    SecretKey derivedKey = kdf.deriveKey("AES", null); 

    System.out.println("Đã tạo khóa bảo mật thành công!");
}

JEP 470: PEM Encodings

  • Hỗ trợ chuẩn hóa định dạng file chứng chỉ (.pem).

JEP 509, 518, 520: JFR (Java Flight Recorder) Enhancements

  • Giám sát chi tiết đến từng mili giây sử dụng CPU của từng Method mà không làm chậm hệ thống.

JEP 508: Vector API (10th Incubator)

  • Cho phép Java tận dụng sức mạnh tính toán song song của CPU (SIMD) cho các tác vụ xử lý ảnh hoặc AI.

5. Kết bài

Như vậy mình và các bạn đã cùng điểm nhanh qua một vài điểm nổi bật của phiên bản JDK25 (LTS). Qua bài viết chúng ta có thể thấy được rằng JDK 25 LTS không chỉ mang lại cú pháp hiện đại giúp lập trình viên viết code nhanh và ít lỗi hơn, mà còn trực tiếp cắt giảm chi phí vận hành thông qua việc tối ưu bộ nhớ (Compact Object Headers) và tăng tốc độ xử lý (Generational Shenandoah, AOT).

Với sự ổn định của một bản LTS, đây là thời điểm vàng để các bạn bắt đầu lộ trình chuyển dịch từ Java 11 hoặc 17 lên một tầm cao mới – nơi hiệu suất và sự tinh giản song hành. Chúc mọi người may mắn, nhớ giữ sức khỏe nhé 💪💪💪


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí