100 ngày thành thạo Java
Lộ trình 100 ngày đi từ cú pháp cơ bản tới JVM, đồng thời và hiệu năng. Mỗi ngày một chủ đề, có sơ đồ và ví dụ chạy được.
100/100 phần đã đăng
Java
1
Cài Java mà không biết tải cái nào: JDK, JRE, JVM, Temurin, Corretto — bản đồ dứt điểm
JDK, JRE, JVM khác nhau ra sao và lồng vào nhau thế nào, vì sao từ Java 11 không còn JRE tải riêng, OpenJDK với Temurin với Corretto là cùng một thứ hay khác, và vì sao nên bám bản LTS.
02/05/2026
· 11 phút đọc
2
Cài JDK 21 đúng cách để một ngày không khổ vì hai dự án chạy hai phiên bản Java
Cài bằng SDKMAN để đổi phiên bản trong một dòng lệnh, chạy file .java trực tiếp không cần biên dịch, dùng jshell để thử nhanh, và gỡ ba lỗi ai cũng gặp trong ngày đầu học Java.
03/05/2026
· 9 phút đọc
3
Một dòng println nở ra sáu lệnh, dấu cộng nối chuỗi biến thành invokedynamic — mở file .class ra xem
javac sinh ra cái gì, constant pool là gì, và vì sao một phép nối chuỗi bằng dấu cộng lại biến thành invokedynamic.
04/05/2026
· 11 phút đọc
4
Integer 127 == 127 cho true, nhưng 128 == 128 cho false — vì sao, và bẫy đó ẩn ở đâu
Tám kiểu nguyên thuỷ, khác biệt giá-trị với tham-chiếu, chuyện đóng hộp mở hộp, bộ nhớ đệm Integer −128..127, và cái NullPointerException đến từ một dòng không có dấu chấm nào gọi phương thức.
05/05/2026
· 10 phút đọc
5
Bốn phép tính Java chạy ngon lành rồi trả về con số sai — không báo lỗi, không cảnh báo
Chia hai số nguyên mất phần dư, tràn int không báo lỗi, 0.1 cộng 0.2 không bằng 0.3, và phép chia số âm không như bạn nghĩ.
06/05/2026
· 10 phút đọc
6
switch kiểu 2005 quên một break là bug lặng — switch kiểu 2025 bắt trình biên dịch vét mọi trường hợp
switch biểu thức không rơi xuống, pattern matching theo kiểu trong switch của Java 21, exhaustiveness với sealed, break có nhãn, và bốn kiểu vòng lặp dùng khi nào.
07/05/2026
· 9 phút đọc
7
Sáu dòng so sánh chuỗi, sáu kết quả true/false — tôi chưa gặp ai đoán đúng cả sáu lần đầu
Vì sao hai chuỗi giống hệt nhau có lúc bằng nhau có lúc không, chuỗi bất biến nghĩa là gì, và nối chuỗi trong vòng lặp chậm hơn StringBuilder bao nhiêu lần — đo thật bằng JMH.
08/05/2026
· 11 phút đọc
8
Object[] a = new String[2] biên dịch trót lọt rồi nổ lúc chạy: lỗ hổng kiểu duy nhất Java để lọt
Mảng là món đồ cổ từ Java 1.0, chơi theo luật riêng: giá trị mặc định, mảng nhiều chiều thực ra là mảng của mảng, equals không giúp được gì, và một đặc điểm về kiểu mà trình biên dịch không hề cảnh báo.
09/05/2026
· 10 phút đọc
9
Java truyền theo giá trị hay tham chiếu? Bốn dòng output chấm dứt ba trăm bình luận cãi nhau
Chấm dứt tranh cãi bằng bốn phép thử chạy được, cộng với quy tắc trình biên dịch chọn phương thức nạp chồng và cái bẫy varargs.
10/05/2026
· 9 phút đọc
10
ClassNotFoundException và NoClassDefFoundError: hai cái tên gần giống nhau, hai nguyên nhân trái ngược
Tổ chức mã theo gói, import tĩnh dùng chừng mực, và một phép thử cho thấy rõ hai lỗi nghe giống nhau nhưng nguyên nhân khác hẳn.
11/05/2026
· 11 phút đọc
11
public long soDu: một dòng cho phép cả dự án đặt số dư = -999 mà không lỗi biên dịch
Trạng thái và hành vi đứng cùng chỗ, trường tĩnh khác trường thể hiện, và ý tưởng quan trọng nhất của chặng OOP — đối tượng tự bảo vệ ràng buộc của chính nó thay vì để logic trôi ra một Service khổng lồ.
12/05/2026
· 11 phút đọc
12
Một trường khai 'đã gán' mà lúc chạy vẫn null: cái bẫy gọi phương thức ghi đè trong hàm khởi tạo
Sáu bước JVM chạy khi bạn gõ new, vì sao một trường khai giá trị rõ ràng lại null giữa chừng, this(...) khác super(...) ra sao, và vì sao gọi phương thức bị ghi đè trong hàm khởi tạo lại nguy hiểm.
13/05/2026
· 11 phút đọc
13
protected không phải 'lớp con thấy được': cái luật truy cập làm cả người đi làm vài năm hiểu sai
Bốn mức truy cập của Java qua thông báo lỗi thật của javac, cái luật protected mà ít người biết (chỉ qua tham chiếu kiểu chính nó), vì sao mức mặc định bị đánh giá thấp, và lý do sinh getter/setter cho mọi trường là thói quen nên bỏ.
14/05/2026
· 10 phút đọc
14
Lớp đếm phần tử: viết bằng kế thừa ra 6, viết bằng thành phần ra 3 — cùng thêm đúng 3 phần tử
Cùng một lớp đếm số lượt thêm, kế thừa HashSet cho ra 6 còn giữ một Set bên trong cho ra 3 — vì addAll âm thầm gọi add. Vì sao 'kế thừa phá vỡ đóng gói', và ba câu hỏi để biết khi nào kế thừa vẫn đúng.
15/05/2026
· 10 phút đọc
15
Cùng một đối tượng, hai giá trị: vì sao phương thức đa hình còn trường thì không
Phương thức được chọn theo đối tượng thật lúc chạy, trường được chọn theo kiểu khai báo lúc biên dịch. Mở bytecode ra là thấy ngay: invokevirtual tra bảng lúc chạy, getfield chốt chỉ mục lúc dịch — cùng một đối tượng cho ra hai kết quả.
16/05/2026
· 10 phút đọc
16
Từ Java 8, ranh giới lớp trừu tượng và interface đã mờ — nhưng hai khác biệt vẫn quyết định bạn chọn cái nào
Interface có phương thức default từ Java 8 nên ranh giới mờ đi nhiều. Hai thứ còn khác thật sự — giữ trạng thái và số lượng kế thừa được — cùng hai câu hỏi để chọn và một ví dụ chuyển từ lớp trừu tượng sang interface.
17/05/2026
· 10 phút đọc
17
Để thêm Stream vào Java 8, họ phải phá vỡ hàng tỷ dòng code — hoặc phát minh một lối thoát
Vì sao interface được phép mang mã sẵn, bài toán kim cương quay lại và được giải bằng Bay.super.diChuyen(), cùng ba luật ưu tiên khi có xung đột.
18/05/2026
· 9 phút đọc
18
map báo size = 1 nhưng get() trả null: khi dữ liệu còn nằm đó mà không cách nào lấy ra
Ba mức độ hỏng của equals/hashCode, từ quên hashCode tới khoá đổi được — kèm ví dụ chạy được cho thấy dữ liệu biến mất khỏi tầm với dù map vẫn báo size = 1, năm điều khoản của hợp đồng, và vì sao record giải quyết gần hết.
19/05/2026
· 11 phút đọc
19
Cùng hai giá trị BigDecimal: HashSet giữ 2 phần tử, TreeSet chỉ giữ 1 — dữ liệu biến mất không dấu vết
Sắp xếp theo nhiều khoá bằng thenComparing, xử lý null, và cái bẫy khi compareTo không nhất quán với equals — cùng dữ liệu, HashSet giữ 2 phần tử còn TreeSet chỉ giữ 1.
20/05/2026
· 9 phút đọc
20
Một listener vài byte ghim sống 100 MB: cái trường this$0 mà trình biên dịch lén thêm vào lớp nội
Bốn loại lớp lồng, một đoạn phản chiếu lôi ra tận mắt cái trường this$0 do trình biên dịch thêm vào, vì sao nó là nguồn của những vụ rò rỉ bộ nhớ khó tìm nhất, và vì sao mặc định nên đặt static.
21/05/2026
· 9 phút đọc
21
record Diem(int x, int y) — một dòng thay cho sáu mươi dòng, nhưng nó vẫn bị sửa lén được
javap cho thấy record sinh sẵn những gì, hàm khởi tạo rút gọn dùng để kiểm tra dữ liệu, và cái bẫy khi thành phần là một List sửa được.
22/05/2026
· 9 phút đọc
22
Thêm một loại mới rồi quên sửa mười chỗ xử lý nó — sealed biến mười quả mìn thành mười lỗi biên dịch
Kiểm soát danh sách lớp con, switch vét cạn không cần default, và phép thử cho thấy điều gì xảy ra khi bạn thêm một loại mới mà quên cập nhật mã xử lý.
23/05/2026
· 9 phút đọc
23
Trình in JSON đầy đủ trong 20 dòng, không một dấu ép kiểu, không một default — mà vẫn kín mọi nhánh
instanceof có khai biến, record pattern lồng nhau, luật che nhánh mà trình biên dịch bắt buộc, và một chi tiết về biến vô danh trên JDK 21.
24/05/2026
· 9 phút đọc
24
Hình vuông KẾ THỪA hình chữ nhật, và một hàm không hề biết nó tồn tại in ra 16 thay vì 20
Mỗi nguyên tắc SOLID một đoạn mã trước và sau, cùng phép thử chạy được cho thấy vi phạm Liskov làm hỏng hàm không hề biết gì về lớp con.
25/05/2026
· 11 phút đọc
25
Hàm ba dòng này ném lỗi rồi nuốt sạch: người gọi nhận 42 như chưa có gì xảy ra
Cây phân cấp Throwable, checked khác unchecked ở đâu, ngoại lệ bị chặn trong try-with-resources, và ba cách nuốt lỗi mà mã vẫn xanh.
26/05/2026
· 9 phút đọc
26
try-finally đóng tài nguyên đúng sách vở vẫn nuốt mất lỗi gốc — bạn đi sửa nhầm chỗ hoàn toàn
Thứ tự đóng ngược, tài nguyên null không gây NPE, cú pháp dùng lại biến có sẵn từ Java 9, và cách tự viết một AutoCloseable đúng chuẩn.
27/05/2026
· 9 phút đọc
27
Ngoại lệ chỉ mang một chuỗi là tờ giấy nhắn 'hỏng rồi' — nó là đối tượng, hãy cho nó mang dữ liệu
Thiết kế ngoại lệ nghiệp vụ có trường riêng, đo chi phí chụp stack trace — 110 ms so với 7 ms cho 200.000 lần — và khi nào nên tắt nó đi.
28/05/2026
· 10 phút đọc
28
log.debug nối chuỗi vẫn tốn thời gian dù DEBUG đã tắt — và bốn thói quen biến log thành vô dụng
Năm mức log dùng khi nào, vì sao nối chuỗi trong log.debug vẫn ngốn CPU khi DEBUG đang tắt, MDC gắn mã đơn vào mọi dòng, và ghi ngoại lệ sao cho không vứt mất stack trace.
29/05/2026
· 9 phút đọc
29
Optional hứa chấm dứt NullPointerException — 12 năm sau, code Java vẫn đầy NPE. Vì sao?
Chuỗi map/flatMap thay cho if lồng nhau, khác biệt orElse và orElseGet đo được bằng một dòng in, và bốn cách dùng Optional chỉ đổi chỗ vấn đề chứ không giải quyết.
30/05/2026
· 9 phút đọc
30
Lớp final, mọi trường private final, có kiểm tra — vẫn bị sửa hai lần, qua hai cửa, không một setter nào
Vì sao final chỉ khoá con trỏ chứ không khoá nội dung, sao chép phòng vệ ở cả cửa vào lẫn cửa ra và phải làm trước khi kiểm tra, và vì sao chọn kiểu bất biến là cách chữa tận gốc.
31/05/2026
· 9 phút đọc
31
Map không phải Collection — và tấm bản đồ chọn chỗ chứa dữ liệu trong 30 giây
Cây giao diện Collections Framework, bảng đối chiếu thứ tự duyệt và khả năng chứa null đo bằng mã chạy được, giao diện Sequenced mới của Java 21, và một bảng chọn nhanh đáng dán cạnh màn hình.
01/06/2026
· 9 phút đọc
32
LinkedList thua ArrayList 49.500 lần ở một phép đo — và vì sao gần như không bao giờ nên dùng nó
Bốn phép đo JMH trên 100.000 phần tử: truy cập ngẫu nhiên, duyệt, thêm cuối, thêm đầu. Chỗ duy nhất LinkedList thắng thì ArrayDeque còn thắng hơn — và bài học lớn hơn là big-O không cho biết cái nào nhanh hơn ở kích thước thật của bạn.
02/06/2026
· 8 phút đọc
33
Tạo 8 khoá trùng băm để xem HashMap dựng cây — mà nó không dựng: luật hai điều kiện ít ai nói đúng
Dùng phản chiếu soi thẳng vào bảng băm JDK 21 để thấy hệ số tải 0,75 hoạt động, va chạm dồn thành chuỗi, và luật hai điều kiện — 8 phần tử VÀ bảng ≥64 ngăn — để một ngăn chuyển sang cây đỏ đen.
03/06/2026
· 9 phút đọc
34
Bộ nhớ đệm LRU: câu phỏng vấn 20 phút, hay sáu dòng nhờ một tham số boolean của LinkedHashMap
Thứ tự chèn khác thứ tự truy cập, tham số accessOrder biến LinkedHashMap thành LRU tự động, removeEldestEntry là phương thức sinh ra để bạn ghi đè, và vì sao đây là chỗ hiếm hoi kế thừa lại đúng.
04/06/2026
· 8 phút đọc
35
Chuỗi if-else bậc giá viết thành một dòng: TreeMap trả lời được câu hỏi HashMap chịu thua
Cây đỏ đen cho phép hỏi những câu HashMap không trả lời được — khoá lớn nhất không vượt quá x, mọi phần tử trong khoảng, và các khung nhìn sống.
05/06/2026
· 8 phút đọc
36
TreeSet sắp theo tuổi rồi âm thầm nuốt mất một người: ba loại Set trả lời 'trùng nhau là gì' ba kiểu
Ba loại Set định nghĩa 'trùng lặp' theo ba cách khác nhau — và TreeSet dùng comparator chứ không dùng equals, nên sắp theo một trường là mất dữ liệu. Kèm số đo contains cho thấy TreeSet chậm hơn HashSet 14 lần.
06/06/2026
· 8 phút đọc
37
In PriorityQueue ra thấy sắp xếp sai, Stack duyệt ngược từ đáy: khi mã đúng còn mắt bạn thì không
Vì sao Stack nên bị bỏ hẳn, hai bộ phương thức của Queue khác nhau ở chỗ nào, và tại sao in PriorityQueue ra lại không theo thứ tự ưu tiên — mã vẫn chạy đúng, chỉ có cách bạn nhìn vào ruột nó là gây hiểu lầm.
07/06/2026
· 8 phút đọc
38
Xoá phần tử áp chót trong for-each: không ngoại lệ, chạy 'vẫn ổn', và lặng lẽ bỏ sót phần tử cuối
Cơ chế modCount và vì sao phép kiểm chỉ nằm trong next() chứ không trong hasNext(), ba cách xoá an toàn, và trường hợp ConcurrentModificationException KHÔNG được ném ra khiến một phần tử bị bỏ sót trong im lặng.
08/06/2026
· 8 phút đọc
39
Ba cách khai 'danh sách không sửa được' trong Java — một trong ba vẫn đổi sau lưng bạn
Khung nhìn chỉ đọc khác bản sao bất biến ra sao và vì sao trả nó ra ngoài là rò rỉ, vì sao Set.of duyệt ra thứ tự khác nhau mỗi lần chạy JVM, và toList() khác collect(toList()) chỗ nào.
09/06/2026
· 8 phút đọc
40
Generics không làm mã ngắn hơn — nó làm lỗi hiện ra lúc gõ thay vì lúc chạy, ở đúng dòng sai
Vì sao generics tồn tại (dời lỗi kiểu từ lúc chạy sang lúc biên dịch), biên trên mở khoá những phương thức nào, và cách bóc một thông báo lỗi suy luận kiểu dài mười dòng thành ba câu dễ hiểu.
10/06/2026
· 9 phút đọc
41
Bốn chữ PECS: vì sao ds.add() không biên dịch, còn ds.get() thì chỉ trả về Object
Hình dung wildcard như hai cái cửa một chiều — máy bán nước chỉ nhả ra, hòm thư chỉ nuốt vào. Hai lỗi biên dịch thật giải thích trọn extends/super, rồi soi lại chữ ký sort và forEach trong JDK.
11/06/2026
· 9 phút đọc
42
Generics của Java biến mất trước khi chạy — và đó là lý do new T[] không biên dịch nổi
Hình dung kiểu generic như nhãn dán trên thùng đồ, bị xé đi trước khi giao hàng. Một dòng code chứng minh, bốn thứ Java cấm vì thế, và phương thức cầu nối mà trình biên dịch lén sinh ra sau lưng bạn.
12/06/2026
· 9 phút đọc
43
contains trong vòng lặp: chạy ngon với dữ liệu thử, treo máy với dữ liệu thật — chậm 875 lần
Bảng chi phí các thao tác collection, ba tình huống chọn sai cấu trúc làm chậm hệ thống thật, và số đo cho thấy dựng một HashSet mới ngay giữa hàm rồi vứt đi vẫn nhanh hơn dùng List.contains trong vòng lặp.
13/06/2026
· 8 phút đọc
44
Viết đúng hai phương thức, nhận miễn phí toString, contains, stream, equals — và một List 6 phần tử không lưu gì
AbstractList cho bạn miễn phí toString, contains, subList, iterator, equals, hashCode. Kèm một danh sách sáu phần tử không lưu phần tử nào.
14/06/2026
· 9 phút đọc
45
Lambda không phải đường cú pháp cho lớp ẩn danh — và khác biệt đó đo được bằng dấu ==
Lambda biên dịch thành invokedynamic cộng một phương thức private, bắt biến cục bộ là chụp một bản sao (nên phải effectively final) còn đọc trường là mở đường dây tới đối tượng, và this trong lambda khác this trong lớp ẩn danh.
15/06/2026
· 9 phút đọc
46
43 functional interface trong Java, nhưng chỉ cần nhớ 4 — phân biệt bằng hình dạng, không bằng tên
Function, Supplier, Consumer, Predicate cùng cách ghép chúng lại, và số đo cho thấy dùng nhầm biến thể (Function<Integer,Integer> thay vì IntUnaryOperator) làm chậm 3,4 lần vì đóng hộp.
16/06/2026
· 8 phút đọc
47
String::toUpperCase và tienTo::concat trông y hệt nhau — nhưng một cái biến tham số thành this
Bốn dạng method reference, chỗ tham số lặng lẽ trở thành this, trường hợp javac báo ambiguous, và khi nào rút gọn lại làm mã khó đọc hơn chứ không dễ hơn.
17/06/2026
· 7 phút đọc
48
Chuỗi Stream đầy đủ, peek có lệnh in — chạy xong màn hình trống trơn. Vì sao?
Vì sao một chuỗi Stream không chạy gì cho tới phép cuối, từng phần tử đi hết chuỗi rồi mới tới phần tử sau, và thứ tự phép quyết định số lần tính toán.
18/06/2026
· 9 phút đọc
49
Ai cũng vấp flatMap lần đầu — không phải vì nó khó, mà vì tài liệu định nghĩa sai chỗ
Đi từ tình huống chứ không từ định nghĩa: khi nào map cho ra thứ lồng nhau, ba lần thật cần flatMap, mẹo giữ ngữ cảnh cả hai tầng, và khác biệt filter với takeWhile mà kết quả tự nói ra.
19/06/2026
· 7 phút đọc
50
groupingBy mạnh không phải vì Java có 40 collector — mà vì chúng lồng được vào nhau
Collector con là chỗ groupingBy trở nên mạnh, gom nhóm hai tầng trong một câu lệnh, partitioningBy luôn trả đủ hai khoá, và toMap ném ngoại lệ khi khoá trùng — vì sao đó là lựa chọn đúng.
20/06/2026
· 8 phút đọc
51
Collector có một hàm 99% thời gian không chạy — rồi ai đó thêm chữ 'parallel', nó hỏng
Bốn hàm của một Collector, dấu vết thật cho thấy khi nào combiner được gọi, và vì sao viết nó sai vẫn không lộ ra trong test.
21/06/2026
· 8 phút đọc
52
Thêm đúng một chữ 'parallel' vào 100 phần tử, và mã chạy chậm đi hai nghìn lần
Bốn phép đo JMH trên máy 16 nhân: khi nào song song thắng bảy lần, khi nào thua hai nghìn lần, và ba câu hỏi để quyết định trước khi gõ chữ parallel.
22/06/2026
· 8 phút đọc
53
isPresent() rồi get() ở dòng dưới: bạn vừa viết một câu if dài dòng chứ chẳng được gì
Bảy phép kết của Stream trả về Optional, cách nối chúng vào chuỗi bằng map và filter thay vì mở hộp ngay, và vì sao or/ifPresentOrElse gọn hơn hẳn if lồng.
23/06/2026
· 7 phút đọc
54
Cùng đếm dòng trong file 130 MB: một cách nổ OutOfMemory với heap 128 MB, cách kia chạy ngon với 61 MB
Tính lười cho phép đọc file lớn hơn cả bộ nhớ, ngắt sớm giúp không đọc phần thừa, và vì sao Files.lines là một trong số ít Stream bắt buộc phải đóng.
24/06/2026
· 8 phút đọc
55
'Stream chậm hơn vòng lặp' — câu được nhắc nhiều nhất về Java, ít khi kèm số liệu nhất
Cộng mảng ngang nhau, lọc và gom nhóm chậm hơn khoảng 10–15%, danh sách nhỏ chậm hơn bốn lần — và vì sao con số đó vẫn không quyết định được bạn nên viết gì.
25/06/2026
· 8 phút đọc
56
Stream khiến mã trông gọn gàng ngay cả khi nó khó hiểu — và đó là cái bẫy
Ba cách viết cùng một phép tính, tách chuỗi không tốn thêm lần duyệt, và bốn dấu hiệu cho biết bạn nên quay về vòng lặp.
26/06/2026
· 8 phút đọc
57
listFiles() trả null cho bốn lý do khác nhau: vì sao nên bỏ hẳn java.io.File
Path là địa chỉ ghi trên phong bì (thao tác chuỗi thuần), Files là chuyến đi giao thật (chạm đĩa), và ba chỗ API java.io cũ nuốt lỗi trong im lặng — trả null hoặc false mà không nói vì sao.
27/06/2026
· 8 phút đọc
58
Tiếng Viá»t có dấu: đọc sai bảng mã chỉ xấu, ghi sai thì mất luôn dữ liệu
UTF-8 thành mặc định từ Java 18, Files.readString ném lỗi trong khi new String âm thầm thay ký tự, NFC khác NFD khiến tên tệp từ máy Mac 'biến mất' trên Linux, và ba byte BOM vô hình làm lệch độ dài chuỗi.
28/06/2026
· 9 phút đọc
59
Chính cha đẻ của Java gọi serialization là 'sai lầm khủng khiếp' — và ước tính nó gây một phần ba số lỗ hổng
Serialization của Java chạy mã trong lúc đọc, gấp ba lần kích thước JSON, lộ cấu trúc lớp trong luồng byte, và cách chặn bằng ObjectInputFilter nếu buộc phải dùng.
29/06/2026
· 8 phút đọc
60
JSON hợp lệ vẫn thành lỗ hổng XSS: cái bẫy khi nhúng dữ liệu Jackson vào thẻ script
record ánh xạ không cần chú thích, trường thiếu về giá trị mặc định trong im lặng còn trường thừa thì ném lỗi, ngày giờ cần module riêng, và vì sao một chuỗi hợp lệ trong JSON lại chạy được mã của kẻ tấn công khi nhúng vào HTML.
30/06/2026
· 8 phút đọc
61
Cộng '1 ngày' và cộng '24 giờ' ra hai kết quả khác nhau — vì hôm đó chỉ có 23 giờ
Chọn giữa Instant, LocalDate và ZonedDateTime bằng ba câu hỏi, một giờ không tồn tại và một giờ lặp lại hai lần trong đêm đổi giờ, và vì sao Period khác Duration đúng hai lần mỗi năm.
01/07/2026
· 9 phút đọc
62
Lịch chạy 8 giờ sáng mỗi ngày, rồi một đêm cuối tháng Ba nó lặng lẽ trượt sang 9 giờ mãi mãi
Ba cái bẫy múi giờ chỉ lộ ra khi hệ thống chạy thật — lưu giờ địa phương là vứt mất thông tin, equals khác isEqual, và một ngày không phải lúc nào cũng 24 giờ.
02/07/2026
· 9 phút đọc
63
Ví dụ ReDoS kinh điển (a+)+b không còn treo JDK 21 — chỗ thật sự đốt 19 giây CPU nằm ở đâu
Nhóm đặt tên, tham lam và không tham lam, biên từ; và một phép đo cho thấy ví dụ ReDoS trong sách giáo khoa đã hết tác dụng với JDK 21, còn kiểu bùng nổ thật thì nằm ở sáu nhóm .* liền nhau — 19 giây cho một chuỗi 120 ký tự.
03/07/2026
· 13 phút đọc
64
connectTimeout không cứu bạn khỏi một dịch vụ treo — và ba mặc định lặng của HttpClient hay cắn
HttpClient có sẵn trong JDK: gọi đồng bộ và sendAsync, hai loại timeout khác nhau mà cái quan trọng lại không mặc định, vì sao mã lỗi HTTP không phải ngoại lệ, và một dấu chấm sai chỗ biến async thành tuần tự.
04/07/2026
· 11 phút đọc
65
Một ô nhập, một chuỗi 28 ký tự, một bảng biến mất — và không ngoại lệ nào được ném ra
JDBC từ đầu: PreparedStatement chặn injection bằng cách tách cú pháp khỏi dữ liệu, getInt trên cột NULL trả về 0, chèn hàng loạt nhanh gấp 74 lần, và setFetchSize bị bỏ qua trong im lặng nếu quên setAutoCommit(false).
05/07/2026
· 12 phút đọc
66
Mở kết nối mới chậm hơn mượn từ pool 15.000 lần — nhưng pool to hơn chưa chắc nhanh hơn
Con số thật sau lời khuyên 'dùng pool', triệu chứng khi pool cạn trông y hệt CSDL chậm, và hai phép đo cho thấy 'pool nhỏ nhanh hơn' đúng lúc nào và sai lúc nào.
06/07/2026
· 11 phút đọc
67
new Thread(viec).run() — dòng này tạo ra một đối tượng luồng rồi không dùng nó, mọi thứ vẫn chạy trên main
Sáu trạng thái của Thread, vì sao Thread.stop() ném lỗi trên Java 21, cái bẫy cờ ngắt bị xoá khi bắt InterruptedException, và 35.000 luồng nền tảng so với 200.000 luồng ảo.
07/07/2026
· 11 phút đọc
68
Hai luồng cùng cộng một triệu lần, kết quả nhảy loạn — trừ đúng một lần ra chính xác, và đó mới đáng sợ
Khoá gắn vào đối tượng nào, khoá tái nhập, thu hẹp phạm vi khoá giảm 5 lần thời gian chạy, và vì sao synchronized làm luồng ảo chậm gấp bốn.
08/07/2026
· 12 phút đọc
69
Một luồng ghi chay=false xong rồi, luồng kia chạy mãi không bao giờ thấy — thiếu đúng một từ khoá
Cờ dừng không bao giờ được nhìn thấy, tái hiện 2/2 lần; lệnh bị đảo thứ tự quan sát được 10 lần trên 2 triệu; và vì sao volatile cứu được cái này nhưng không cứu nổi i++.
09/07/2026
· 12 phút đọc
70
Không khoá luôn nhanh hơn khoá? Ở 32 luồng, AtomicLong thua synchronized 38%
So sánh-và-đổi (CAS) hoạt động ra sao, đo số lần phải thử lại tăng theo mức tranh chấp (16 luồng: 9 triệu lần thử hỏng cho 3,2 triệu phép cộng), bài toán ABA cùng cách chữa, và vì sao LongAdder giữ tốc độ gần như phẳng tới 32 luồng khi AtomicLong sụp.
10/07/2026
· 10 phút đọc
71
Bật khoá công bằng 'cho chắc' — và vừa làm hệ thống chậm đi 2.600 lần
tryLock thoát khỏi deadlock mà synchronized chịu chết, cái giá thật của khoá công bằng, và bảng đo cho biết khi nào tách đọc–ghi mới đáng.
11/07/2026
· 12 phút đọc
72
Hàm chuyển tiền sáu dòng treo cứng sau 0,11 giây — và cách jstack chỉ thẳng vào dòng lỗi
Dựng deadlock có chủ đích từ một hàm chuyển tiền trông hoàn toàn hợp lý, đọc kết quả jstack thật để JVM tự chỉ ra chu trình chờ, sửa bằng thứ tự khoá nhất quán, và ba biến thể deadlock mà jstack không hề phát hiện.
12/07/2026
· 11 phút đọc
73
Đổi if thành while trong wait() — mã chạy hoàn hảo 80.000 lần, rồi treo khi tải đổi
Viết tay sản xuất – tiêu thụ bằng wait/notify để thấy hai lỗi kinh điển hỏng ra sao, rồi viết lại bằng BlockingQueue trong sáu dòng.
13/07/2026
· 11 phút đọc
74
newFixedThreadPool và newCachedThreadPool: hai dòng code tiện tay là hai quả bom hẹn giờ
Vì sao hai kiểu thread pool phổ biến nhất đều không có áp lực ngược, submit() nuốt ngoại lệ trong im lặng, bốn chính sách khi hàng đợi đầy, và bảng đo kích thước pool cho tải tốn CPU ngược hẳn tải chờ I/O.
14/07/2026
· 11 phút đọc
75
CompletableFuture và cái pool ba luồng dùng chung cả JVM
supplyAsync không kèm executor chạy trên pool chung — và pool ấy chỉ có ba luồng trên máy bốn nhân, dùng chung với parallelStream và mọi thư viện. Một tác vụ tầm thường đợi 31 giây vì xếp hàng sau 21 tác vụ chặn.
15/07/2026
· 10 phút đọc
76
Cùng một mã fork/join, chỉ đảo thứ tự hai dòng là chậm 12 lần — và đường cong chữ U của ngưỡng chia nhỏ
Bảng đo ngưỡng chia nhỏ từ 100 tới 50 triệu vẽ ra một đường cong chữ U, cơ chế ăn trộm việc kéo đủ 15 luồng vào một tải lệch hẳn, và ba cách viết fork/join mà một cách chậm hơn 12 lần chỉ vì gọi join() quá sớm.
16/07/2026
· 10 phút đọc
77
8 luồng ghi vào một HashMap, mất một nửa dữ liệu — và vì sao synchronizedMap an toàn mà vẫn sai
Vì sao HashMap mất dữ liệu khi nhiều luồng ghi, synchronizedMap nguyên tử từng thao tác mà vẫn sai ở mẫu kiểm-tra-rồi-hành-động, computeIfAbsent khác containsKey-rồi-put chỗ nào, và bảng tỷ lệ ghi để biết khi nào CopyOnWrite sụp.
17/07/2026
· 10 phút đọc
78
Request của người này đọc ra tên người khác: ThreadLocal trong pool luồng không phải lỗi hiệu năng, là lỗ hổng bảo mật
ThreadLocal dùng để làm gì, hai cái bẫy khi có pool luồng — rò dữ liệu giữa các request và giữ lại 110MB sau GC — và ScopedValue của Java 21.
18/07/2026
· 10 phút đọc
79
Một luồng ảo đi qua năm luồng mang khác nhau — trừ khi nó bị ghim, và lúc đó lợi ích bốc hơi
Một luồng ảo đi qua năm luồng mang khác nhau, chi phí thật 800 byte mỗi luồng, chẩn đoán ghim bằng JDK Flight Recorder, và một lời khuyên phổ biến mà phép đo không ủng hộ.
19/07/2026
· 10 phút đọc
80
Một dịch vụ hỏng sau 104ms, cái kia vẫn chạy tiếp 3 giây để tạo ra kết quả bị vứt đi
StructuredTaskScope gom vòng đời tác vụ con vào một phạm vi: huỷ tự động khi có lỗi, hạn chờ cho cả nhóm, và cây phạm vi hiện ra trong ảnh chụp luồng.
20/07/2026
· 10 phút đọc
81
'Không thể ép Rieng thành Rieng' — khi cùng một file .class biến thành hai lớp khác nhau
Ba tầng nạp lớp và cây uỷ quyền cha-trước, vì sao danh tính một lớp là cặp (tên, loader) chứ không phải cái tên, vì sao lần chạm thứ hai làm mất nguyên nhân gốc, và cách phân biệt ClassNotFoundException với NoClassDefFoundError.
21/07/2026
· 11 phút đọc
82
Đặt -Xmx512m, tiến trình Java chiếm 111 MB khi chưa làm gì — phần RAM còn lại nằm ở đâu
Native Memory Tracking chỉ ra từng vùng bộ nhớ JVM, vì sao 500 luồng đặt chỗ hơn 1GB mà chỉ dùng thật 37MB, năm kiểu hết bộ nhớ với năm thông báo khác nhau, và vì sao container Java chết với mã 137 mà log sạch trơn.
22/07/2026
· 10 phút đọc
83
javap: lambda hoá ra không phải lớp ẩn danh, và + nối chuỗi đã nhanh hơn StringBuilder
Đọc bytecode bằng javap — công cụ có sẵn mà gần như không ai mở: máy ngăn xếp, bốn lệnh gọi, lambda thật ra là invokedynamic sinh lớp lúc chạy chứ không phải lớp ẩn danh, và switch trên chuỗi chạy hai bước băm-rồi-so.
23/07/2026
· 10 phút đọc
84
Cùng một hàm, chạy lần thứ 500 nhanh gấp 22 lần lần đầu — vì sao mọi phép đo Java một-lần đều vô nghĩa
C1 và C2, các tầng trong log PrintCompilation, ngưỡng 200 và 5000, nội tuyến hoá là vũ khí chính, và một lần khử tối ưu bắt tận tay — kèm chuyện phép đo không cho thấy nó tốn kém.
24/07/2026
· 10 phút đọc
85
Cùng 400 MB cấp phát, thời gian dừng chênh nhau 10 lần — và vì sao MaxGCPauseMillis=10 làm mọi thứ tệ hơn
Giả thuyết thế hệ đo bằng số thật, bốn bộ thu gom trên cùng một tải với ZGC dừng 0,025ms, và vì sao đặt mục tiêu tạm dừng quá thấp lại phản tác dụng — chi phí GC tỷ lệ với thứ còn sống, không phải thứ đã chết.
25/07/2026
· 10 phút đọc
86
Tạm dừng dưới 1 mili giây đổi lấy 79% thông lượng — và vì sao Shenandoah mới là bất ngờ của phép đo
Đo độ trễ theo phân vị trên bốn bộ thu gom. Cái giá thật của tạm dừng dưới một mili giây không nằm ở chỗ bạn nghĩ, và cái tên thắng phần đuôi không phải cái tôi đoán lúc vào bài.
26/07/2026
· 10 phút đọc
87
GC log nhìn như bức tường số vô nghĩa — nhưng ba con số nói cho bạn biết heap chật hay đang rò rỉ
Giải phẫu một dòng GC log, ba con số cần nhìn trước tiên, cách phân biệt heap chật với rò rỉ bộ nhớ chỉ bằng grep, và một lỗi tôi tự mắc khi cộng nhầm cả pha đồng thời rồi ra 104,8%.
27/07/2026
· 10 phút đọc
88
Biểu đồ heap bảo byte[] chiếm 96% — và đó là thông tin vô dụng: cách thật sự tìm chỗ rò rỉ
Chụp heap bằng jcmd, vì sao lớp đứng đầu biểu đồ luôn là byte[] và không nói lên điều gì, kỹ thuật so hai ảnh chụp chỉ cần JDK để tìm thủ phạm, và vì sao dominator tree của MAT mới là màn hình quan trọng.
28/07/2026
· 9 phút đọc
89
Bật hồ sơ suốt ngày chỉ tốn 3,6% — để 3 giờ sáng có sẵn dữ liệu thay vì phải chờ sự cố xảy ra lần nữa
Đo chi phí thật của JDK Flight Recorder, hồ sơ cấp phát chỉ ra 303MB Integer từ một phép đóng hộp giấu trong dấu ngoặc, và vì sao async-profiler thấy được cả GC lẫn JIT mà JFR không thấy.
29/07/2026
· 9 phút đọc
90
Tôi dựng bốn cái bẫy microbenchmark kinh điển — ba cái không hề xảy ra, và cái tệ nhất không có trong sách
Dựng microbenchmark sai rồi đối chiếu với JMH: loại bỏ mã chết, gấp hằng số, thiếu làm nóng, nhiễm bẩn giữa các phép đo. Ba cái không xảy ra như sách vở nói — còn cái cắn thật lại là chuyện đo đúng con số rồi kể sai câu chuyện.
30/07/2026
· 10 phút đọc
91
Đảo hai dòng trong pom.xml, ứng dụng chạy phiên bản thư viện khác — không một cảnh báo nào
Vòng đời build thực sự chạy những gì, năm phạm vi phụ thuộc và classpath tương ứng, và vì sao thứ tự khai báo lại lặng lẽ quyết định phiên bản bạn chạy.
31/07/2026
· 10 phút đọc
92
Maven build lại còn chậm hơn build sạch của chính nó — và vì sao cùng file khai báo, hai công cụ chạy hai thư viện khác nhau
Đo cùng một dự án 300 lớp trên Maven và Gradle: chỗ Gradle thật sự nhanh hơn (và chỗ không), vì sao build lại của Maven chậm hơn build sạch, và một khác biệt chọn phiên bản khiến hai bên chạy hai thư viện khác nhau mà không báo.
01/08/2026
· 9 phút đọc
93
'expected: true but was: false' — dòng báo lỗi vô dụng nhất, và cách để test không bao giờ nói thế
Vòng đời JUnit 5 in ra bằng số thật (mỗi test một đối tượng mới, thứ tự không theo khai báo), ba dòng test theo tham số thay cho mười một, và khác biệt giữa thông báo lỗi hữu ích với thông báo chẳng nói gì.
02/08/2026
· 10 phút đọc
94
Tôi tái cấu trúc code mà không đổi hành vi — một test xanh, một test đỏ. Đỏ mới là test viết sai
Một phép thử cho thấy verify quá tay biến test thành xi măng, mock trả gì khi chưa khai báo, ArgumentCaptor, spy, và những chỗ tuyệt đối không nên mock.
03/08/2026
· 9 phút đọc
95
Bốn trong chín câu SQL: H2 nói 'được', PostgreSQL nói 'không' — và luôn lệch về phía nguy hiểm
Chạy cùng bộ SQL trên H2 và PostgreSQL 16 để thấy chỗ chúng khác nhau, đo chi phí thật của việc khởi động container, và cách giữ test nhanh khi dùng CSDL thật.
04/08/2026
· 10 phút đọc
96
900 lần chạy test không bắt được lỗi luôn nằm đó — thêm một dòng sleep, nó lộ ra 100%
Vì sao test đồng thời xanh không chứng minh gì, bảng đo cho thấy cùng một lỗi lộ ra 0% hay 100% tuỳ khe hở, vì sao Thread.yield gần vô dụng, và vì sao ngủ cố định trong test vừa chập chờn hơn vừa chậm hơn hỏi lại.
05/08/2026
· 9 phút đọc
97
Cùng một jar, ảnh Docker từ 866 MB xuống 126 MB — và cái bẫy làm ảnh nhỏ nhất in tiếng Việt ra dấu hỏi
Năm cách đóng gói cùng một ứng dụng Java, kích thước đo được từ 866 xuống 126 MB, jlink cắt runtime chỉ còn java.base, và một cái bẫy bảng mã chỉ lộ ra khi bạn in chữ có dấu lên console.
06/08/2026
· 9 phút đọc
98
log.debug('x'+tính()) vẫn chạy hàm tính() dù DEBUG tắt — và ba lăng kính bạn cần trước khi lên sản xuất
Đo chi phí thật của bốn cách gọi log (nhanh chậm 20 lần), so log văn bản với JSON, và ba lăng kính quan sát — log, chỉ số, truy vết — mà thiếu một cái là mù một nửa khi hệ thống hỏng.
07/08/2026
· 9 phút đọc
99
5 dòng phụ thuộc, 73 lỗ hổng — và không lỗ nào do tôi khai
Quét phụ thuộc trên một pom.xml năm dòng ra 73 lỗ hổng đã biết, toàn bắc cầu; XXE đọc trọn /etc/passwd với cấu hình mặc định của Java; và bản đồ những lỗ hổng Java hay gặp nhất kèm cách chặn từng cái.
08/08/2026
· 10 phút đọc
100
Một trăm ngày Java, mười lần phép đo nói ngược điều ai cũng tin
Nhìn lại một trăm bài Java: bảy chặng đã đi qua, mười phép đo bác lại lời khuyên hiệu năng vẫn được lặp như chân lý, và tấm bản đồ để đi tiếp từ đây.
09/08/2026
· 7 phút đọc