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