5 điểm bởi GN⁺ 4 giờ trước | 2 bình luận | Chia sẻ qua WhatsApp
  • JEP 401: Value Classes and Objects đã đạt đến giai đoạn được đưa vào JDK preview thực tế
  • Mục tiêu cốt lõi là khiến đối tượng Java có thể “lập trình như class, vận hành như int”, qua đó giảm chi phí của header đối tượng, cấp phát heap, GC và tham chiếu gián tiếp qua con trỏ
  • Value class trong JDK 28 hiện vẫn là kiểu tham chiếu có thể null, chưa bao gồm kiểu non-null, generic chuyên biệt hóa hay mã hóa 128-bit, và cần --enable-preview
  • JVM có thể scalar hóa value object hoặc làm phẳng trên heap trong field và mảng, nhưng trong các kiểu cha như generic đã xóa kiểu hoặc Object, nó có thể được materialize thành đối tượng trên heap
  • Lập trình viên Java cần phản ánh sự khác biệt giữa identity và value trong thiết kế mã nguồn; điều này ảnh hưởng đến ==, synchronized, primitive wrapper, hiệu năng mảng và cả chuyên biệt hóa generic trong tương lai

Phạm vi của Valhalla được đưa vào JDK 28

  • Ngày 15 tháng 6, kỹ sư Oracle Lois Foltan xác nhận JEP 401: Value Classes and Objects đã được tích hợp vào kho chính của OpenJDK và nhắm tới JDK 28
  • Pull request liên quan đã bổ sung hơn 197 nghìn dòng trên 1.816 tệp
  • Do quy mô thay đổi quá lớn, trong lúc tích hợp đã có yêu cầu các committer khác tạm hoãn những commit lớn
  • JEP 401 là một tính năng preview bị tắt mặc định
    • Cần --enable-preview để dùng cú pháp
    • Brian Goetz nhấn mạnh đây là “phần đầu tiên của Valhalla”
  • JDK 28 dự kiến phát hành vào tháng 3 năm 2027, còn việc tích hợp vào mainline được lên kế hoạch vào khoảng tháng 7 năm 2026

Chi phí của mô hình đối tượng Java mà Valhalla nhắm tới

  • Khẩu hiệu của Valhalla là “codes like a class, works like an int
    • Mục tiêu là vẫn dùng class thông thường với method, kiểm tra constructor và tên field có ý nghĩa, nhưng để JVM xử lý hiệu quả như primitive
  • Trong Java, ngoại trừ 8 primitive, gần như mọi thứ đều là kiểu tham chiếu
    • Trong Point p = new Point(1, 2), p không phải bản thân point mà là con trỏ trỏ tới đối tượng trên heap
    • Mỗi lần đọc field, JVM phải lần theo con trỏ
  • Khi số lượng đối tượng tăng lên, chi phí tăng rất nhanh
    • Mỗi đối tượng có header đối tượng để lưu kiểu, trạng thái đồng bộ hóa và các thông tin khác
    • Đối tượng được cấp phát trên heap và sau đó trở thành đối tượng của GC
    • Một mảng gồm một triệu Point thực tế được tạo thành từ một triệu con trỏ và một triệu đối tượng nằm rải rác trên heap
  • “State of Valhalla” của Brian Goetz gọi cách bố trí bộ nhớ này là fluffy
    • Điều Valhalla hướng tới là bố trí dense, nơi dữ liệu được đặt sát nhau

Khoảng cách phần cứng và giới hạn của escape analysis

  • Lý do bố trí bộ nhớ dày đặc quan trọng là vì khoảng cách tốc độ giữa CPU và bộ nhớ
    • Năm 1995, chi phí truy cập bộ nhớ gần tương đương phép tính trên CPU
    • Hiện nay CPU nhanh hơn bộ nhớ chính cả một bậc độ lớn hai chữ số, và cache lấp đầy khoảng cách đó
  • CPU thường đọc bộ nhớ theo đơn vị cache line 64 byte
    • Nếu dữ liệu dày đặc và nằm tuần tự, một lần đọc sẽ mang về nhiều giá trị hữu ích
    • Nếu phải lần theo con trỏ đến các đối tượng nằm rải rác, có thể xảy ra cache miss và chậm hơn rất nhiều so với hit
  • Escape analysis của JVM có thể loại bỏ một số lần cấp phát đối tượng
    • Nếu xác định được đối tượng không “escape” ra ngoài đoạn mã cục bộ, JVM có thể không cấp phát nó trên heap mà trải các field ra thành biến hoặc thanh ghi
  • Tuy nhiên escape analysis kém ổn định và khá mong manh
    • Nếu đối tượng đi vào field của class khác, được lưu vào mảng, được truyền qua method phức tạp hoặc vượt qua ranh giới mà JIT không phân tích được, tối ưu hóa có thể dừng lại
    • Chỉ một lần refactor nhỏ, cập nhật JDK hoặc thay đổi cấu trúc mã cũng có thể khiến đối tượng lại bị đưa lên heap
  • Nếu từ bỏ đối tượng để tự mã hóa trực tiếp thành các byte thô như r, g, b nhằm lấy hiệu năng, bạn sẽ có tốc độ nhưng mất đi tính an toàn, khả năng đọc hiểu, kiểm tra và method

Khởi đầu năm 2014 và bước chuyển từ Q World sang L World

  • Project Valhalla chính thức bắt đầu vào năm 2014
  • James Gosling khi đó mô tả nó là “six PhDs tied into a single knot”
  • Những người tạo ra Java đã muốn có value type từ thời Java 1.0, nhưng vào năm 1995 bài toán này quá khó nên họ đã bỏ qua
  • Mục tiêu ban đầu là khôi phục sự căn chỉnh giữa mô hình lập trình và đặc tính hiệu năng của phần cứng hiện đại
    • Hướng đi là cho phép người dùng tự khai báo kiểu phẳng và dày đặc như primitive, nhưng vẫn trông và hoạt động như class thông thường
  • Prototype ban đầu đi theo hướng Q World
    • Cách này xem value type là thực thể khác biệt về căn bản so với đối tượng, với type descriptor, bytecode và top type riêng
    • Toàn bộ hệ thống kiểu của JVM vì thế phải mang hai biến thể, làm độ phức tạp tăng mạnh
  • L World xuất hiện vào khoảng năm 2019 là bước ngoặt
    • Value type chia sẻ cùng một “L carrier” với reference thông thường
    • Nhóm phát triển từng dự đoán việc hợp nhất này sẽ khó, nhưng nó hoạt động mà không cần thỏa hiệp lớn và giải quyết được nhiều vấn đề của prototype trước đó
  • Trong L World xuất hiện một sự tách biệt quan trọng
    • Mô hình JVM và mô hình ngôn ngữ không nhất thiết phải trùng khớp 100%
    • JVM có thể giữ mô hình L World, còn với lập trình viên thì cung cấp một mô hình ngôn ngữ thuận tiện hơn
  • Sau đó, công việc được chia thành hai giai đoạn: value class và generic chuyên biệt hóa

Sự thay đổi của tên gọi và mô hình

  • Thuật ngữ Valhalla đã thay đổi nhiều lần, và đây không chỉ là đổi tên mà còn phản ánh sự thay đổi về mô hình
  • Thuật ngữ ban đầu là value types
    • Khi đó vẫn chưa thực sự rõ các kiểu này chính xác là gì
  • Khoảng 2019~2020, mô hình inline classes dần được định hình
    • Class hiện có được xem là identity classes, còn class mới được phân biệt là inline classes không có identity
    • inline class mặc định là final, các field là final, và có ràng buộc không thể đồng bộ hóa
  • “State of Valhalla” năm 2021 đề cập đến primitive classes và mô hình hai projection
    • Ý tưởng là một kiểu sẽ có biến thể value phẳng, không cho phép null, và biến thể reference cho phép null
    • Các cú pháp như Point.val / Point.ref, sau đó là Point! / Point?, cũng từng được thử nghiệm
  • Mô hình này mạnh mẽ nhưng tạo ra gánh nặng nhận thức lớn
    • Lập trình viên phải thường xuyên hiểu hai dạng của cùng một kiểu và thời điểm chuyển đổi giữa chúng
    • Cuối cùng, để đơn giản hóa mô hình cho người dùng, tính lưỡng nguyên đã được thu hẹp lại
  • Hiện tại, JEP 401 dùng các khái niệm value classvalue object
    • value class được khai báo bằng modifier value
    • instance là value object không có identity
    • value class vẫn là một reference type
  • Tính non-nullability được tách thành một JEP tùy chọn riêng là Null-Restricted Value Class Types
    • Không có trong JDK 28
  • Những bài viết cũ giải thích mô hình “primitive classes” trước đây có thể khác với tiêu chuẩn OpenJDK hiện tại
  • Trong JEP 401 còn có JEP 402: Enhanced Primitive Boxing ở trạng thái preview
    • Hướng đi là làm cho việc chuyển đổi giữa primitive và wrapper mượt mà hơn
    • Không nên giả định rằng mọi thứ sẽ cùng đi vào JEP 401 ở trạng thái hoàn thiện

Mô hình value class trong JDK 28

  • value class được khai báo bằng modifier value
value class USDCurrency implements Comparable<USDCurrency> {  
    private int cents; // implicitly final  
    public USDCurrency(int dollars, int cents) {  
        this.cents = dollars * 100 + cents;  
    }  
  
    public USDCurrency plus(USDCurrency that) {  
        return new USDCurrency(0, this.cents + that.cents);  
    }  
  
    // dollars(), cents(), compareTo(), toString()...  
}  
  • Cũng có thể có value record
  • Các quy tắc chính như sau
    • Tất cả instance field đều ngầm định là final
    • method không thể là synchronized
    • class mặc định là final
    • Có thể tạo hệ phân cấp gồm value class và abstract value class
    • Không thể kế thừa class có identity
    • Có thể implement interface
  • Đặc tính cốt lõi là không có identity
    • Với object thông thường, dù nội dung giống nhau, nếu tạo hai lần bằng new Point(1, 2) thì đó vẫn là hai object khác nhau
    • Với value object, không có identity, giống như với giá trị int là 4 thì không tồn tại “hai số 4 khác nhau”

Thay đổi của ==, synchronized, và null

  • Với value object, == không còn là so sánh identity mà trở thành phép kiểm tra substitutability
    • Nó so sánh đệ quy xem có cùng class và cùng giá trị các field hay không
    • primitive field được so sánh ở mức bit, còn object field lại tiếp tục được so sánh bằng ==
    • new USDCurrency(3,95) == new USDCurrency(3,95) sẽ cho kết quả true
  • Tuy vậy, vì == nhìn vào trạng thái bên trong, nên để kiểm tra “có biểu diễn cùng một dữ liệu hay không” thì thường equals vẫn phù hợp hơn
  • value object không có identity để đồng bộ hóa
    • Nếu cố đồng bộ hóa sẽ phát sinh IdentityException
    • Khi cần xác nhận bắt buộc về identity, có thể dùng Objects.requireIdentityObjects.hasIdentity
  • value class trong JDK 28 vẫn có thể là null
    • USDCurrency d = null; là hợp lệ
    • Kiểu cấm null sẽ là một JEP tương lai riêng, không có trong JDK 28
  • non-nullability không chỉ là vấn đề cú pháp đơn thuần mà còn là một đòn bẩy hiệu năng mở đường cho việc làm phẳng các value class lớn hơn

Scalarization và heap flattening

  • JEP 401 cho JVM quyền tự do tối ưu hóa value object
  • Scalarization là kỹ thuật JIT phân rã reference của value object thành một tập field
    • Thay vì truyền con trỏ Color, có thể truyền các byte r, g, b và một cờ null hay không
    • Chi phí cấp phát và GC có thể biến mất
    • Nó giống escape analysis nhưng dễ dự đoán hơn và có thể áp dụng vượt qua ranh giới của các lời gọi method không được inline
  • Scalarization có những giới hạn
    • Nếu kiểu của biến là Object, tức supertype của value class, hoặc là tham số generic đã bị erase, thì thường sẽ không hoạt động
    • Khi đó object phải được materialize trên heap
  • Heap flattening là cách mã hóa các giá trị field của value object thành một vector bit gọn và ghi trực tiếp vào field hoặc phần tử mảng
    • Không cần con trỏ trỏ đến vị trí heap khác
    • Điều này tạo ra mật độ dữ liệu và locality tốt hơn
  • Dữ liệu đã được làm phẳng phải có thể được đọc và ghi atomic để tránh tearing khi truy cập đồng thời
    • Trên các nền tảng phổ biến, kích thước “đủ nhỏ” có thể ở mức 64 bit, bao gồm cả cờ null
    • Value class nhỏ có thể được làm phẳng tốt, nhưng chỉ cần có hai field int hoặc một double thôi cũng có thể không khớp với kích thước ghi atomic và vì vậy trở thành object heap thông thường
  • Trong tương lai, mã hóa 128 bit và kiểu null-restricted có thể cho phép làm phẳng các value class lớn hơn

Tác động trong boxing, wrapper, và mảng

  • Khi bật preview, chính các primitive wrapper class như Integer, Long, Double sẽ trở thành value class
    • Box sẽ mất identity nên JVM có thể scalarize và flatten chúng
    • Integer[] sẽ tiến gần đến hiệu quả của int[], theo hướng giảm đáng kể boxing overhead
  • JEP 402: Enhanced Primitive Boxing mở rộng thêm việc chuyển đổi giữa primitive và box
    • Nó mở đường tới các biểu thức như List<int>, nhưng hiện vẫn là một nhánh công việc riêng đang tiếp tục hoàn thiện
  • Hiệu quả thể hiện rõ nhất trong mảng
    • Color[] trước đây có thể trở thành một triệu con trỏ và một triệu object nằm rải rác trên heap
    • Color[] của value class có thể trở thành một contiguous block lưu trực tiếp các giá trị màu liên tiếp
    • CPU có thể đọc tuần tự nhiều giá trị theo đơn vị cache line

Khác biệt trước và sau qua ví dụ Point[]

  • Ví dụ về class thông thường trước thời Valhalla như sau
final class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • Mảng này chứa một triệu con trỏ
    • Mỗi con trỏ trỏ tới một đối tượng Point riêng biệt ở đâu đó trên heap
    • Mỗi đối tượng ngoài hai int còn có cả object header
    • Khi duyệt, phải đọc con trỏ, nhảy tới địa chỉ đó, rồi đọc các field
  • Sau Valhalla, ví dụ về value class sẽ như sau
value class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • Khác biệt trong code chỉ là một từ value, nhưng cách bố trí bộ nhớ sẽ thay đổi
    • JVM có thể lưu giá trị của từng point một cách dày đặc ngay trong mảng
    • Không có header cho từng element và cũng không có con trỏ
    • Với hai intx, y, chúng có thể được sắp liên tiếp thành 8 byte cùng với null flag nếu có
  • Tính dễ bảo trì cũng vẫn được giữ nguyên
    • Point vẫn là một class có tên, constructor, kiểm tra hợp lệ và method
    • Có thể tránh cách tách thành int[] xs, int[] ys rồi phải canh chỉ số cho khớp

Vì sao specialized generics vẫn còn ở phía trước

  • Java generics được triển khai bằng type erasure
    • List<String>List<Integer> là cùng một List ở runtime
    • type parameter T bị erasure thành Object
  • Erasure là lựa chọn có chủ đích để đưa generics vào Java mà không phá vỡ codebase hiện có
    • Ngay cả khi đổi class không generic thành generic, các source file và compiled class cũ vẫn không bị phá vỡ
  • Valhalla và erasure xung đột với nhau về hiệu năng
    • Nếu đưa value object vào List<Point>, vì T bị erasure thành Object, đối tượng sẽ phải được materialize trên heap
    • Lợi ích flattening có được từ Point[] có thể biến mất trong ArrayList<Point>
  • Kế hoạch khắc phục có hai bước
    • Universal Generics: ở cấp độ ngôn ngữ, cho phép type variable xử lý cả value type
      • Vẫn dùng erasure
      • Vì field T mặc định bắt đầu từ null, có thể xuất hiện compiler warning về “null pollution”
      • Nếu xử lý được các cảnh báo này, API sẽ tiến gần tới trạng thái sẵn sàng cho specialization
    • Specialized Generics: ở cấp độ JVM, tạo class layout chuyên biệt cho từng concrete type argument
      • Trong thuật ngữ của dự án, species và type restriction có liên quan ở đây
      • Chỉ tới bước này ArrayList<Point> mới có thể thực sự dùng bộ nhớ phẳng
  • JDK 28 không có full specialized generics
    • Việc để collection, stream và API trở nên phẳng và không cần cấp phát trên value type là phần việc của các bản phát hành tương lai

Những gì có và không có trong JDK 28

  • Những gì sẽ có trong JDK 28 gồm
    • khai báo value classvalue record
    • migration sang value class cho các value-based class hiện có trong JDK như primitive wrapper
    • scalarization và flattening cho các class đáp ứng điều kiện
    • boxing rẻ hơn
  • Những gì không có trong JDK 28 gồm
    • null-restricted types
    • full specialized generics
    • mã hóa 128 bit
    • JEP 402 chưa hoàn thiện đầy đủ
  • Vì là preview feature, syntax và hành vi có thể thay đổi qua từng bản phát hành dựa trên feedback
  • JDK 28 không phải LTS
    • LTS tiếp theo nhiều khả năng là JDK 29 vào tháng 9/2027
    • Nhiều công ty có thể sẽ gặp Valhalla đã ổn định trong một bản LTS, nhưng bản preview của JDK 28 sẽ khởi động vòng phản hồi với code thực tế

Những thay đổi sẽ đến với hệ sinh thái và code

  • Trong các lĩnh vực Java hiệu năng cao, Valhalla mở ra con đường xử lý dữ liệu dày đặc mà không phải từ bỏ abstraction
    • Bao gồm các lĩnh vực như xử lý dữ liệu, tính toán vector, ML, phát triển game, tài chính và codec
  • Framework và library có thể bắt đầu migration các value-based class
  • Code phụ thuộc vào identity có thể gặp khác biệt về hành vi
    • == trên value object sẽ không còn là so sánh địa chỉ mà là so sánh substitutability
    • synchronized trên value object sẽ dẫn tới IdentityException
  • Ngay cả khi Integer trở thành value class, trong đa số trường hợp binary vẫn sẽ tiếp tục link được
    • Lỗi biên dịch mới sẽ xuất hiện khi cố đồng bộ hóa trên các kiểu như vậy
    • == dựa vào identity của Integer hoặc synchronized(someInteger) có thể bị ảnh hưởng
  • Bản dựng early-access có thể dùng tại jdk.java.net/valhalla

Tóm tắt các câu hỏi thường gặp

  • value class khác với record
    • record là lựa chọn coi content là component
    • value là lựa chọn từ bỏ identity
    • Có thể có đủ các tổ hợp: class thường, record, value class và value record
  • Có thể so sánh value object bằng ==
    • Nhưng ý nghĩa là substitutability chứ không phải so sánh địa chỉ
    • Với tính tương đương của dữ liệu được biểu diễn, equals thường phù hợp hơn
  • value class trong JDK 28 vẫn có thể là null
    • non-nullable type là một JEP trong tương lai
    • Điều này cũng quan trọng với flattening của các value class lớn hơn
  • Chưa có ArrayList<Point> phẳng và nhanh
    • Vì type erasure, đối tượng trong collection generic sẽ được materialize trên heap
    • Trong JDK 28, các trường hợp tiêu biểu mà flattening hoạt động trực tiếp là field của value type và mảng như Point[]
  • escape analysis không thể thay thế tất cả
    • Nếu đối tượng đi vào field, mảng hoặc vượt ra ngoài ranh giới phân tích, tối ưu hóa có thể bị phá vỡ
    • Scalarization của value object dễ dự đoán hơn và có thể đi xa hơn qua các ranh giới gọi method
  • Toàn bộ Valhalla sẽ được mở rộng qua nhiều bản phát hành
    • JDK 28 là preview đầu tiên của value class
    • specialized generics, null-restricted types và mã hóa 128 bit là các phần việc trải dài qua những bản phát hành tương lai

2 bình luận

 

Các luồng ảo của Project Loom, được phát hành sau một thời gian dài, khá tiện lợi và giải quyết được nhiều thứ ở cấp độ runtime của JVM, nên nhà phát triển có ít điều phải bận tâm hơn.
Hy vọng Project Valhalla khi được phát hành chính thức cũng sẽ mang lại cảm giác gần như một bữa trưa miễn phí như vậy.

 
Ý kiến trên Hacker News
  • Dù nói khác biệt về bộ nhớ là phần cốt lõi, tôi vẫn nghi ngờ bài này có được biên tập tử tế hay không
    Chẳng phải ngay trước đó bài vừa giải thích rằng các đối tượng có biểu diễn vượt quá 64 bit thì không được làm phẳng trên heap sao? Point trong ví dụ có 2 số nguyên 32 bit cộng thêm cờ null, vậy tối thiểu là 65 bit
    Cụm “có thể có cờ null” cùng với các câu nhấn mạnh ngắn đi kèm phía sau khiến tôi có cảm giác AI đã tạo các câu nhấn mạnh rồi đi chệch hướng, còn khối "[IMAGE: the same Point[] array in two variants..." ở giữa thì thật đáng tiếc

    • Những câu như “Không có header cho từng phần tử. Không có con trỏ. Không phải nhảy khắp heap.” mang mùi văn phong AI, nên trông như kiểu viết lười biếng
      Dùng AI để hỗ trợ viết thì không sao, nhưng nếu không đưa tiếng nói của chính mình vào thì chẳng có lý do gì để đọc
      https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
    • Chủ đề này trông thực sự rất thú vị nên tôi đã muốn đọc, và đến cả ảnh tạo bằng AI tôi vẫn còn có thể bỏ qua
      Nhưng chỉ sau vài đoạn là quá rõ đây là bài viết được tạo bằng cách cho qua LLM, hoặc còn tệ hơn thế
      Dù là blog kỹ thuật hay gì khác, làm ơn đừng để AI viết thay. Không ai muốn đọc kiểu bài đó cả
    • Có tới 18446744073709551616 giá trị khả dĩ, mà không thể dành 1 trong số đó cho null sao? :)
      Hôm nay tôi mới biết trong Rust có NonZeroU64, và khi kết hợp với Optional thì có thể đạt được hành vi cần thiết chỉ với 64 bit cho mỗi mục
      https://doc.rust-lang.org/std/num/type.NonZeroU64.html
    • Rõ ràng là dùng AI quá nhiều nên tôi đọc 2 đoạn rồi dừng luôn
    • Câu “các đối tượng có biểu diễn vượt quá 64 bit thì không được làm phẳng trên heap” là chuyện của commit ban đầu
      Như JEP cũng nói rất rõ, đây chỉ là sản phẩm đầu tiên của một tính năng khổng lồ, và cũng như các tính năng Java gần đây, nó đang được chuyển giao theo từng mảnh
      Mục tiêu dĩ nhiên là làm phẳng cả các giá trị lớn hơn, và cơ chế đó đã có sẵn trong JVM. Việc còn lại là bộc lộ ở cấp ngôn ngữ ý định “cho phép bị xé”
  • Tôi ghi nhận công sức bỏ vào Valhalla, nhưng khó mà đồng ý với cách diễn giải rằng “mô hình thì mạnh nhưng quá nặng về mặt tinh thần”
    Việc nói một biến không thể là null không phải là một sự phân biệt gây gánh nặng nhận thức, nhất là khi mọi thứ đều được chú thích đầy đủ
    Thái độ “hy sinh trần hiệu năng để đơn giản hóa mô hình cho người dùng” thực ra có khi lại chính là một cách đơn giản hóa cho người dùng
    Hệ thống kiểu của ngôn ngữ lập trình tồn tại để cung cấp cho lập trình viên những bảo đảm tiện lợi trên một CPU chỉ xử lý số. Không cần phải giảm bớt các bảo đảm an toàn tùy chọn chỉ vì cho rằng chúng “quá phức tạp”
    Thậm chí người ta còn đã đi tới nhận thức rằng “mô hình ngôn ngữ và mô hình JVM không nhất thiết phải trùng khít 100%” mà vẫn như vậy

    • Tôi không mấy tin tưởng Java có thể đi đúng hướng
      Cơ chế quản trị Java có vẻ thiếu sót, và điều đó đặc biệt tương phản với phía .NET, nơi phần lớn đã đưa ra quyết định đúng ngay từ đầu
      Gần đây tôi còn nghi ngờ không biết Java có còn giá trị hay nhận được sự quan tâm nào bên trong Oracle nữa không. Công ty giờ trông như một mảng kinh doanh datacenter/compute với hoạt động di sản và khoản nợ khổng lồ bám theo
      Có lúc tôi tự hỏi liệu ở Oracle có phải các bộ phận còn kiếm ra tiền chỉ là pháp lý và cắt cỏ hay không
    • Chẳng phải sự khó chịu đó đang nhắm vào blogger chứ không phải ngôn ngữ Java sao?
      dấu chỉ null cũng sẽ đến: https://openjdk.org/jeps/8303099
      Chỉ là phải phát hành dần dần thôi, mà riêng PR này để đưa value class/object vào đã ở quy mô 200 nghìn dòng rồi
    • Kiểu giá trị không cho phép null chỉ là phần được để dành cho JEP tiếp theo
      Có vẻ không phải là nói nó bất khả thi, mà là không thể ăn cả con voi trong một miếng
      Dù vậy thì cũng phải nói là họ đã gặm cái chân voi này khá lâu rồi
    • nullable chỉ là một trạng thái tải khác trong lập trình hướng đường ray
      Nếu đó là khái niệm đã được giải quyết từ năm 2012 thì không có lý do gì phải đưa trực tiếp nhiều hương vị trạng thái vào ngôn ngữ. Đường ray chỉ đi tới A hoặc B, còn việc rẽ theo hướng nào thì phụ thuộc vào trạng thái tải của đoàn tàu
      Nếu một khái niệm cứ xuất hiện rồi biến mất và chiến tranh ngôn ngữ lại nổ ra, đó là dấu hiệu cho thấy có nhu cầu nhưng ngôn ngữ либо không xử lý được đúng nhu cầu đó, либо có xử lý thì cũng tạo ra quá tải nhận thức
      Chẳng hạn như Value, Errorstates, Null, IoExceptions, WeirdOsStatesNeededToHandleUpstairs
      https://fsharpforfunandprofit.com/rop/
      Nói theo kiểu Monty Python thì giờ ta tiến lên tiếp đi
    • Điều đang được nói ở đây không phải null safety mà là phép chiếu tham chiếu/giá trị tương tự Integer/int
      Thay vì có một phép chiếu có định danh và một phép chiếu không có định danh cho mỗi kiểu, nhóm Valhalla đã làm cho value type hoàn toàn không có định danh, và vì thế Integer với int trở thành từ đồng nghĩa
      Bố cục bộ nhớ được quyết định tự động theo ngữ cảnh và các quyết định tối ưu hóa. Vì vậy, ý nghĩa của == đối với các wrapper nguyên thủy như Integer cũng đã thay đổi, và giờ không còn phụ thuộc vào việc bạn dùng “phép chiếu tham chiếu” hay “phép chiếu giá trị” nữa
      Ở đây không hề có chuyện giảm bớt các bảo đảm an toàn tùy chọn chỉ vì cho rằng chúng “gây gánh nặng tinh thần”
  • Trong các bình luận trên HN liên quan đến Java/JVM, điều lặp đi lặp lại là có nhiều người đến mức đáng ngạc nhiên vẫn giữ hình ảnh cũ về JVM hay Java, nhưng gần như không biết gì về diện mạo ngày nay của nó
    JVM của năm 2026 là một kẻ săn mồi cực kỳ khỏe mạnh. Có khuyết điểm không? Tất nhiên là có, nhưng nền tảng thì cực kỳ vững chắc

    • Trên HN rất khó để thấy JVM được đánh giá tích cực, ở đây nó bị xem như công nghệ đã hết thời
      Trong công việc tôi dùng Java 26 mới nhất cùng các tính năng preview, chủ yếu là StructuredConcurrency, và nó rất tuyệt. Ngay cả với tư cách người từng dùng Haskell và Python ở các công ty trước, tôi cũng hoàn toàn không hối tiếc
    • Nhiều người đang phải bảo trì các monolith Java bắt đầu từ thời Java bùng nổ trong những năm 2000, và vẫn phải chạy chúng trên Java 8
      Cá nhân tôi có biết các tính năng mới xuất hiện vài năm gần đây, nhưng trong công việc thực tế thì Java đúng nghĩa là vẫn bị mắc kẹt trong quá khứ
  • Khá nhiều bình luận ở đây có phần không công bằng so với những công việc tuyệt vời đang diễn ra hiện nay và các JEP còn thú vị hơn sẽ ra mắt sau này
    Nếu ví Java như một đứa trẻ, thì vài năm đầu nó lớn lên với cha mẹ đầy yêu thương (Sun), rồi sau đó bị quăng vào gara cùng những đứa trẻ khác và bị bỏ mặc bởi một người giám hộ độc ác (Oracle)
    Nó đã bị bỏ mặc và không được yêu thương cho đến tận JDK 8, nên về cơ bản suốt thời gian qua là chạy để bắt kịp
    Nói rằng “đến giờ mới có struct hay value type” là đúng, nhưng đó là vì sự phát triển của nó bị kìm hãm bởi quy trình doanh nghiệp khổng lồ, quan liêu và thù địch. Giờ thì nó đã được tự do và đang được yêu thương thông qua gia đình OpenJDK
    Chúng ta sẽ còn tiếp tục tận hưởng niềm vui viết một lần, triển khai mọi nơi

    • Thích hay ghét Oracle thì cũng phải nói rằng đó không phải là mô tả đúng về lịch sử Java
      Gần với sự thật hơn là nó được cha mẹ yêu thương nuôi lớn, rồi vì vấn đề tài chính mà bị gửi vào gia đình nhận nuôi tạm thời, nơi nó bị bỏ mặc
      Sau đó nó được nhận nuôi bởi cha mẹ mới đầy yêu thương là Oracle, và Java đã nở rộ thành một người trưởng thành khỏe mạnh, ổn định
      Chính Oracle là bên biến OpenJDK thành bản triển khai tham chiếu và hoàn tất việc mã nguồn mở nền tảng, đồng thời cũng mã nguồn mở các công cụ trước đây là độc quyền như JFR và Mission Control
      Họ cũng giữ lại nhiều thành viên đời đầu của nhóm ngôn ngữ, điều khá hiếm trong các vụ thâu tóm kiểu này, và Java đã được cải thiện lớn cả ở ngôn ngữ lẫn runtime
    • Java bị bỏ mặc là vào những năm cuối cùng dưới thời Sun
      Oracle đã đẩy Java tiến lên với tốc độ chưa từng có trong khi vẫn giữ được phần lớn tính tương thích ngược
      Người ta nói .NET là “làm đúng ngay từ đầu”, nhưng nếu điều đó đồng nghĩa với việc tách và viết lại thành .NET Framework/.NET Core/.NET thì ngay trong cuộc thảo luận này cũng không hợp lý. .NET đáng lẽ có thể học từ Java mà vẫn làm hỏng ở một số chỗ
      MySQL cũng vậy. Trên trang này người ta bảo nó “đã chết”, nhưng với những người thực sự hiểu thì nó đã hồi sinh dưới Oracle
    • Câu “Oracle bỏ mặc Java” và “nó bị bỏ mặc cho đến JDK 8” tự mâu thuẫn với nhau
      Phiên bản Java cuối cùng dưới thời Sun ra mắt năm 2006, Oracle mua Sun năm 2010, còn JDK 7 ra năm 2011 và JDK 8 ra năm 2014
      Đội ngũ nhìn chung vẫn như cũ, và khác biệt lớn nhất là Oracle đã chấm dứt sự bỏ mặc đó và rót thêm tiền. Vì vậy sau vụ thâu tóm, tốc độ của Java đã tăng lên
      Người ta nói là “bắt kịp”, nhưng cũng không rõ là bắt kịp ai. Chỉ có JS/TS và Python là những ngôn ngữ nổi tiếng ngang hoặc hơn Java
      Những người bảo Java bị tụt hậu thường lại so với các ngôn ngữ đang làm kém hơn Java rất nhiều. Những người yêu một tính năng cụ thể thường bỏ qua việc ngôn ngữ có tính năng đó vẫn đang ì ạch không phải nhờ tính năng ấy mà là bất chấp nó
      Các nhà quản lý thích phát hành nhanh, trong khi giới lãnh đạo kỹ thuật từ thời Sun lại chủ trương phải cẩn trọng, chậm mà chắc
      Tôi hiểu cảm giác rằng Java không còn nổi như năm 2003, nhưng thời kỳ đó là giai đoạn hợp nhất hiếm có không chỉ với Java mà với cả hệ sinh thái phần mềm nói chung, và trước sau đó cũng chưa từng thống nhất như vậy
    • Nói là “viết một lần, triển khai mọi nơi” nhưng lại không chạy được trên trình duyệt, iOS hay hệ thống nhúng
      Công nghệ thực sự viết một lần, triển khai mọi nơi bây giờ là WebAssembly. JVM đã có thời của mình và đã thua
    • Nếu tiếp tục phép ẩn dụ này, Java không chỉ bị quăng vào gara mà còn bị đem ra dùng để kiện Google đòi hàng tỷ đô tiền cấp dưỡng, và cuối cùng trở thành một công cụ thu tiền mặt
      Dù vậy tôi cũng không gọi đó là bị “kìm hãm phát triển”. Nó đã đưa ra các lựa chọn, một số hợp lý, một số thì không, và những lựa chọn đó về sau cực kỳ khó sửa
      Chỉ cần nhìn C++ cũng thấy, cá nhân tôi xem tính tương thích ngược một phần với C như một con albatross dài 150 foot không thể sửa nổi, và nhiều phiên bản từ C++11 trở đi chỉ là nỗ lực làm cho cái gánh nặng đó dễ chịu hơn một chút
      Trên JVM, việc xử lý mọi value class như một L-type duy nhất giống các kiểu nguyên thủy là một lời giải khá gọn cho một bài toán khó
      Cuối cùng thì tất cả chuyện này đều bắt nguồn từ quyết định của Java 2 là triển khai generics bằng type erasure để giữ tương thích ngược, và C3 đã nhìn vào kết quả đó rồi từ chối đi theo con đường ấy
  • Chỉ riêng quá trình tiến hóa của value type trong Java thôi cũng có thể viết thành cả một cuốn techno-thriller
    Tôi đã đọc mailing list và xem hết các video liên quan, và thực sự ấn tượng với quá trình họ hợp nhất thiết kế thành một thứ lúc nào cũng trông rất đúng chất Java
    Đồng thời họ cũng đào sâu hơn rất nhiều vào việc value type thực sự có nghĩa là gì, cũng như những tối ưu hóa nào có thể thực hiện ở đâu

    • Thay đổi cú pháp chỉ là thêm mỗi value
  • Với value class, == về cơ bản sẽ hoạt động giống như memcmp()
    Điều này hơi đáng tiếc, vì nó phá vỡ tính đóng gói và làm lộ chi tiết triển khai
    Mã phía client có thể rẽ nhánh tùy theo cách một giá trị được biểu diễn nội bộ. Ở một khía cạnh nào đó, điều này còn tệ hơn so với so sánh định danh, vì so sánh định danh ít nhất không làm lộ trạng thái bên trong

    • Value type là một khái niệm rất xa với kiểu tư duy hướng đối tượng “sinh vật hộp đen ma thuật”
      Đây không phải là làm hướng đối tượng cổ điển theo cách mới, mà là một ngôn ngữ sinh ra từ lý tưởng hướng đối tượng tiến thêm một bước vào thế giới hậu hướng đối tượng
    • Nếu một khối dữ liệu có trạng thái nội bộ, thì chính khối dữ liệu đó đã có vấn đề
      Tôi nghĩ phía Java hẳn cũng đã cân nhắc đầy đủ chuyện loại trừ padding khỏi phép so sánh hoặc ép các byte padding về 0
      Điều này cũng phải áp dụng được cho chuỗi. Chuỗi rõ ràng vẫn sẽ tiếp tục được cấp phát trên heap, và việc memcmp các con trỏ bên trong một “struct” mới chính xác là so sánh định danh
    • Cốt lõi của value class là không được đóng gói trạng thái, tức là nó phải là một vật chứa dữ liệu hoàn toàn minh bạch
    • Nếu chưa từng dùng Java trong thực tế, có thể bạn sẽ không cảm nhận được ý nghĩa thật sự của thay đổi này. Đây là một thay đổi phá vỡ tương thích hiếm hoi mà Java thực hiện
      Java tách biệt việc kiểm tra định danh của đối tượng và kiểm tra tính tương đương. == về cơ bản dùng để xem hai con trỏ có giống nhau hay không, còn tính tương đương là một khái niệm chủ quan dựa trên các giao diện như equals/hashCode
      Vì vậy new Integer(1000) == new Integer(1000) trước đây là false nhưng giờ sẽ thành true, còn new Integer(1000).equals(new Integer(1000))true, và new Integer(10) == new Long(10) trước đây là false nhưng giờ trở thành lỗi biên dịch
      Trong Java trước đây, các số nguyên dưới một ngưỡng nhất định thường được thay bằng kiểu đã được canonical hóa, hình như tôi nhớ là quanh mức 128. Vì thế mới có khác biệt giữa 10 và 1000
      Bây giờ có vẻ các phép so sánh bên trên được unboxing ngầm. Việc so sánh Integer/Long trước đây là false mà giờ thành lỗi biên dịch cho thấy chắc chắn có sự can thiệp của unboxing
      Có lẽ với biến thì vẫn có thể lấy lại hành vi cũ
      Dù sao đi nữa, khi value class mất đi định danh, == sẽ chuyển từ tương đương con trỏ sang tương đương ở mức bit. Tôi hy vọng họ sẽ xử lý được các trường hợp góc cạnh như vậy, nhưng về mặt kỹ thuật thì đây là thay đổi phá vỡ tương thích
  • Tôi hiểu mục đích của value class, nhưng cách triển khai có khuyết điểm
    Đoạn mã sau sẽ in ra gì? Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);
    Cho tới nay câu trả lời luôn rõ ràng, nhưng khi thêm value class thì đáp án sẽ khác tùy Point là value class hay reference class. Vì vậy thiết kế này làm hại tính dễ đọc
    Đây là vi phạm nguyên tắc tính đồng nhất. Trong 『The Psychology of Computer Programming』 của Weinberg, tính đồng nhất được mô tả là nguyên tắc tâm lý theo đó người dùng kỳ vọng những gì trông giống nhau sẽ hoạt động giống nhau, còn những gì trông khác nhau sẽ hoạt động khác nhau
    Khi một ngôn ngữ lập trình cho phép hai cú pháp trông gần như giống hệt nhau tại điểm sử dụng lại có hành vi ngữ nghĩa khác nhau, gánh nặng nhận thức của người đọc sẽ tăng lên. Muốn biết phép gán, tính tương đương, định danh và thay đổi có vận hành như đối tượng tham chiếu thông thường hay như giá trị, bạn phải kiểm tra khai báo kiểu hoặc dựa vào công cụ
    Có thể sửa điều này nếu bắt buộc dùng từ khóa value không chỉ lúc khai báo mà cả lúc sử dụng. Ví dụ viết như value Point a = new Point(10, 10);

    • Nhìn vào mục tiêu của JEP 401 thì điều đó là không thể. Value class nhằm cho phép lập trình viên chọn mô hình lập trình cho dữ liệu bất biến
      https://openjdk.org/jeps/401
    • Đọc bài thì dòng thứ 3 là lỗi cú pháp. Mọi trường của value type đều là final
      Tất nhiên chỉ nhìn bốn dòng đó thì không có cách nào biết chuyện này sẽ xảy ra, nhưng vấn đề đó hiện giờ cũng đã tồn tại. Nếu Point là record thì tình huống tương tự vẫn xảy ra
    • Câu lệnh a.x = 100; sẽ không hợp lệ. Record type là bất biến
      Vì vậy tình huống đang lo ngại lẽ ra là không thể xảy ra
    • Dù vậy vẫn hơi mơ hồ
      Nếu viết như value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x); thì sẽ rõ ràng hơn nhiều rằng có xảy ra sao chép/nhân bản, và việc thay đổi một trường sẽ không ảnh hưởng đến trường của đối tượng khác
  • Tôi biết trong thế giới Java thì việc thừa nhận .NET tồn tại có thể bị xem là bất lịch sự, nhưng tôi tò mò không biết cái này khác struct của .NET như thế nào
    Nếu nhìn lướt qua value type, chuyên biệt hóa generic và boxing thì có vẻ như họ đã đưa ra cùng một lựa chọn

    • Trong C# thực sự có khá nhiều cái bẫy, và Java đặt mục tiêu làm phần này rõ ràng hơn
      Nếu C# về cơ bản sao chép C từ góc nhìn cấp thấp, thì phía Java tiếp cận từ cấp cao hơn và phân tích kỹ những ràng buộc nào mang lại lợi ích gì
      Ở các ngôn ngữ khác, việc phân loại struct/class là nhị phân, còn Java cho phép kiểm soát tinh vi hơn để phản ánh ngữ nghĩa của miền cơ sở
      Và người ta cũng nhận ra struct có nhiều khẩu súng chĩa vào chân khác nhau, đặc biệt trong ngữ cảnh song song
    • Bài viết có một phần nói về chuyện đó
      Cá nhân tôi xem struct của C/C# là có thể thay đổi và được truyền bằng bản sao, còn value class thì không thể thay đổi và được truyền bằng giá trị
      Tôi nghĩ Java không thể cấp phát trên stack
    • Về mặt chức năng thì không khác, Java giờ chỉ đang bắt kịp một thông lệ cũ mà thôi
      Kiểu lưỡng phân giả tạo rằng “struct của C# có định danh và khả năng thay đổi nên phải định nghĩa chính xác ngữ nghĩa sao chép khi gán hay truyền, vì thế tạo ra mô hình nặng hơn cho lập trình viên và ít tự do hơn cho runtime” thực ra không khớp lắm với điều đang được giải thích
      Nó có thể không có định danh theo nghĩa tham chiếu lớp của Java, nhưng theo nghĩa là một cấu trúc bộ nhớ duy nhất tại một địa chỉ nhất định thì hiển nhiên nó vẫn có định danh. Điều này gần như là bắt bẻ câu chữ quanh thuật ngữ của Java
  • Chú thích 6 “Nó khác struct của C# thế nào” là không chính xác
    Nhìn vào việc bài có đầy hình ảnh do AI tạo thì tôi tự hỏi có phải trong quá trình viết, hoặc ít nhất là quá trình tìm hiểu, cũng có lẫn rất nhiều nội dung bịa/hallucination hay không

  • Bài viết hơi mơ hồ và kịch tính, nhưng may là các tài liệu gốc khá dễ đọc
    Trang cấp cao nhất: https://openjdk.org/projects/jdk/28/spec/
    Trạng thái JEP: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
    Giá mà có ai đó theo dõi giúp các bước phát triển liên quan của C#, Swift, Java và Rust. Theo tôi, tất cả đều đang cạnh tranh để bắt kịp phần cứng và đồng thời ảnh hưởng lẫn nhau
    Cá nhân tôi lo ngại những thay đổi này sẽ ảnh hưởng thế nào đến chia sẻ bộ nhớ FFI