Lý thuyết Java Core
Bản diễn giải tiếng Việt dựa trên Java SE 21, JLS 21 và các JEP đã phát hành. Thuật ngữ tiếng Anh được giữ lại ở nơi cần tra cứu hoặc phỏng vấn.
- Mô hình thực thi
- Kiểu và biến
- Biểu thức và control flow
- Class và OOP
- Design Patterns
- Java hiện đại
- Equality và String
- Exception và resource
- Generics
- Collections
- Collections Internals
- Sorting algorithms
- Lambda và Stream
- Concurrency và JMM
- Async và virtual thread
- I/O, time và reflection
- JVM, GC và diagnostic
- Bản đồ chuyên đề mở rộng
1. Mã nguồn, bytecode, JDK và JVM
Java source được compiler kiểm tra cú pháp và kiểu rồi biên dịch thành bytecode trong file .class. JVM nạp class, kiểm tra bytecode, liên kết symbolic reference và thực thi bằng interpreter hoặc JIT compiler. Tính “write once, run anywhere” đến từ bytecode chuẩn cùng JVM tương ứng trên từng nền tảng; nó không có nghĩa mọi hành vi phụ thuộc hệ điều hành đều giống nhau.
| Thành phần | Vai trò |
|---|---|
| JVM | Máy ảo thực thi bytecode, quản lý memory, thread, GC và runtime services. |
| JRE | Khái niệm môi trường chạy gồm JVM và thư viện runtime. Từ JDK 11, Oracle không còn cung cấp download JRE và Server JRE riêng; JDK 9–10 vẫn có các download này. |
| JDK | JVM, thư viện và công cụ phát triển như javac, java, javadoc, jcmd. |
Ví dụ nhập môn theo luồng hướng dẫn chính thức: chương trình đầu tiên
public class HelloJava {
public static void main(String[] args) {
System.out.println("Xin chào Java 21!");
}
}javac HelloJava.java
java HelloJavaTên file phải trùng public top-level class. main là entry point truyền thống; String[] args chứa argument từ command line.
Đừng trộn compiler, class loader và JIT
Contract: loading tìm biểu diễn class; linking gồm verification, preparation và resolution.
Resolution có thể diễn ra sớm hoặc khi symbolic reference được dùng, không nhất thiết resolve toàn bộ chương trình
trước main. Initialization mới thực thi static initializer theo quy tắc ngôn ngữ.
Interpreting/JIT là cách triển khai runtime, không phải cam kết mọi method đều được JIT hay đều nhanh hơn sau
một số lần gọi cố định. [R02 · JLS 12: Execution] · [R03 · JVMS 2: Runtime areas]
Thực hành build: lưu chương trình đầu tiên thành HelloJava.java, chạy lệnh
ở thư mục chứa file. Kết quả là Xin chào Java 21!. Lệnh dưới ghi rõ encoding nguồn và release
đích; --release không tự chứng minh dependency bên thứ ba tương thích runtime đích. [javac, Java 21]
javac -encoding UTF-8 --release 21 HelloJava.java
java HelloJava
Encoding đầu ra: nếu terminal hiển thị sai dấu tiếng Việt, kiểm riêng encoding/font của terminal và encoding source; biên dịch UTF-8 không tự cấu hình mọi console.
java -version, javac -version, lệnh build và output. Giải thích được vì sao source hợp lệ nhưng vẫn có thể gặp lỗi liên kết hoặc lỗi môi trường khi chạy.2. Hệ thống kiểu, biến và chuyển đổi
Java là ngôn ngữ statically typed: mỗi expression có kiểu được xác định lúc compile. Có tám primitive type: boolean, byte, short, int, long, char, float, double. Reference type gồm class, interface, array và type variable. Biến reference giữ một reference hoặc null, không chứa trực tiếp object.
Field được gán default value; local variable phải được compiler chứng minh đã khởi tạo trước khi đọc. Numeric promotion và conversion tuân theo quy tắc cụ thể; narrowing conversion có thể mất dữ liệu nên thường cần cast.
int exact = 100;
long widened = exact; // widening: an toàn về miền giá trị int
int narrowed = (int) 3_000_000_000L; // overflow: kết quả không còn 3 tỷJava luôn truyền tham số theo giá trị
Method nhận một bản sao của giá trị argument. Với primitive, đó là bản sao số/boolean/char. Với object, đó là bản sao của reference. Hai biến có thể cùng trỏ đến một object nên mutation của object được quan sát từ cả hai nơi, nhưng gán lại parameter không đổi reference của caller.
static void rename(Person person) { person.name = "An"; }
static void replace(Person person) { person = new Person("Bình"); }Widening không đồng nghĩa giữ mọi chữ số
Contract: int → long giữ nguyên giá trị, nhưng một số widening sang
floating-point có thể mất precision. Cast 3_000_000_000L sang int trong ví dụ nguồn
cho -1294967296, không ném lỗi chỉ vì vượt miền int. Unboxing một wrapper
null gây NullPointerException. Primitive không phải object; wrapper như
Integer mới là reference type. [JLS 5]
int n = 16_777_217;
float f = n;
System.out.println((int) f); // 16777216: mat precision
Integer missing = null;
// int value = missing; // Neu bo comment: NullPointerException
Đọc ví dụ truyền tham số: giả sử Person có field String name
và constructor Person(String name). Với p = new Person("Lan"),
rename(p) sửa object mà p đang trỏ tới thành tên “An”;
replace(p) không thay object mà biến p của caller đang trỏ tới.
final trên reference ngăn gán lại biến, không làm object được tham chiếu thành immutable.
rename/replace. Vẽ hai biến reference cùng trỏ một object, thay vì mô tả Java là pass-by-reference.3. Expression, statement và control flow
Expression tạo ra một value hoặc side effect; statement tạo nên luồng thực thi. Cần nắm precedence nhưng nên dùng ngoặc khi biểu thức khó đọc. && và || short-circuit; toán hạng phải không còn cần thiết thì phần sau không chạy. switch hiện có thể là expression và yêu cầu các nhánh phủ đủ miền giá trị.
String label = switch (status) {
case NEW -> "Mới";
case RUNNING -> "Đang chạy";
case DONE -> "Hoàn tất";
};Array có độ dài cố định, là object và kiểm tra bounds tại runtime. Enhanced for phù hợp khi không cần index hoặc sửa cấu trúc collection.
“Phủ đủ nhánh” có phạm vi cụ thể
Ví dụ nguồn giả định enum Status { NEW, RUNNING, DONE } và một biến
Status status khác null. Mỗi nhánh → trả một nhãn; block nhiều
statement trong switch expression dùng yield để trả giá trị. Không nhầm với
switch kiểu case ...: có thể fall-through. [JLS 14] · [JLS 15]
Contract: exhaustive khi compile không đồng nghĩa null-safe. Với Java 21, selector
reference bằng null cần nhánh case null nếu muốn xử lý nó trong switch;
chỉ có default không bắt null. Khi enum/sealed hierarchy được biên dịch lại riêng và
thêm biến thể, binary cũ cũng không được bảo đảm xử lý mọi giá trị mới. [JEP 441]
Quy tắc đọc code: trong x != null && x.isReady(), điều kiện
đầu bảo vệ lời gọi sau. Enhanced for không cấp quyền sửa cấu trúc collection tùy ý:
chọn API sửa phù hợp với implementation, thay vì trông chờ vòng lặp tự xử lý mutation.
4. Class, interface và thiết kế hướng đối tượng
Class mô tả state và behavior; object là instance. Encapsulation bảo vệ invariant bằng cách giới hạn quyền sửa state. Inheritance biểu diễn quan hệ “is-a” và cho phép subtype polymorphism, nhưng tạo coupling mạnh. Composition biểu diễn “has-a”, thường linh hoạt và dễ test hơn.
Initialization
Static field/block chạy khi class được initialization. Instance field initializer và instance initializer chạy trước constructor body, sau phần constructor của superclass. Tránh gọi overridable method từ constructor vì subclass có thể chưa được khởi tạo đầy đủ.
Interface hay abstract class?
| Interface | Abstract class |
|---|---|
| Định nghĩa capability/contract; một class có thể implement nhiều interface. | Chia sẻ state, protected implementation và constructor cho một hierarchy có quan hệ chặt. |
| Có abstract, default, static và private method theo phiên bản Java hiện đại. | Có instance field và đầy đủ mức truy cập. |
Nested class gồm static nested class và inner class. Inner instance giữ liên kết ngầm đến outer instance; điều này ảnh hưởng lifetime và memory, nên không dùng vô thức.
Dispatch khác với lựa chọn overload
Contract: overload được chọn lúc compile theo kiểu khai báo của biểu thức và các quy tắc áp dụng method. Với instance method có thể override, runtime dispatch chọn implementation theo object thực tế. Static method được hide chứ không override như instance method; field access cũng không có dynamic dispatch theo cách đó. [JLS 8] · [JLS 15]
Phạm vi Java 21: “đã load class” chưa có nghĩa static block đã chạy. Trong quá trình tạo instance, superclass được khởi tạo trước phần instance initializer và constructor body tương ứng của subclass. Gọi method overridable từ constructor có thể đi vào subclass khi state subclass vẫn chưa hoàn chỉnh. [R02 · JLS 12: Execution]
Trade-off thiết kế: chọn inheritance khi subtype thực sự giữ contract và invariant của base type, không chỉ để dùng lại vài dòng code. Chọn composition khi cần thay dependency, test độc lập hoặc tránh ràng buộc lifecycle. Interface không phải “class không có code”: default/static/private method có vai trò riêng; abstract class phù hợp khi hierarchy cần state và construction chung.
5. Record, sealed class và pattern matching
Record là dạng class dành cho aggregate dữ liệu trong suốt. Compiler tạo private final component fields, accessor, canonical constructor, equals, hashCode và toString. Record shallowly immutable: component reference không đổi, nhưng object được tham chiếu vẫn có thể mutable.
record Money(long amount, String currency) {
Money {
if (amount < 0) throw new IllegalArgumentException("amount");
}
}Sealed class/interface kiểm soát trực tiếp các subtype được phép bằng permits. Nó hữu ích khi domain có tập biến thể hữu hạn. Pattern matching cho instanceof và switch kết hợp type test với binding variable, giúp code xử lý hierarchy rõ và exhaustiveness được compiler hỗ trợ.
sealed interface PaymentResult permits Paid, Declined {}
record Paid(String transactionId) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}
String message = switch (result) {
case Paid(var id) -> "Thành công: " + id;
case Declined(var reason) -> "Từ chối: " + reason;
};Những tính năng ở đây không cần preview trên Java 21
| Tính năng | Ổn định từ | Phạm vi cần nhớ |
|---|---|---|
| Record | Java 16 [JEP 395] | Data carrier; không tự deep-copy component. |
| Pattern matching cho instanceof | Java 16 [R04 · JEP 394] | Binding variable có flow scope. |
| Sealed class/interface | Java 17 [JEP 409] | Kiểm soát direct subtype; subtype tiếp tục sealed, final hoặc non-sealed theo quy tắc. |
| Record patterns | Java 21 [R05 · JEP 440] | Paid(var id) là record pattern, không chỉ type pattern. |
| Pattern matching cho switch | Java 21 [JEP 441] | Exhaustiveness, dominance và null handling là các vấn đề riêng. |
Để chạy đoạn PaymentResult, đặt các khai báo sealed/record trong cùng file bài tập,
khai báo PaymentResult result = new Paid("tx-01"); trước switch trong method.
Kết quả nhánh đó là Thành công: tx-01. Ví dụ Money chỉ minh họa compact
constructor kiểm amount; chưa kiểm currency, đơn vị tiền hoặc toàn bộ invariant nghiệp vụ.
record Team(java.util.List<String> members) {
Team {
members = java.util.List.copyOf(members);
}
}
Scope guarantee: List.copyOf ở đây tạo list không sửa được và tách
khỏi thay đổi cấu trúc của list đầu vào; không deep-copy phần tử. Các phần tử đang là
String immutable. Nếu thay bằng object mutable, vẫn phải định nghĩa ownership hoặc
copy phần tử. [R06 · List API]
--release 21 không bật preview. Kiểm amount âm, nhánh Paid/Declined, null selector và mutation list đầu vào; giải thích record giữ được invariant nào, không giữ được invariant nào.6. Equality, hash code, immutability và String
== so sánh primitive value hoặc identity của reference. equals biểu diễn logical equality do class định nghĩa. Contract yêu cầu reflexive, symmetric, transitive, consistent và trả false với null. Nếu hai object equal thì chúng bắt buộc có cùng hash code; chiều ngược lại không bắt buộc.
Key của hash-based collection nên giữ ổn định các field tham gia equals/hashCode. Thay đổi chúng sau khi insert có thể khiến entry “mất” trong bucket cũ.
String immutable, hỗ trợ sharing và string pool. Concatenation lặp trong loop nên dùng StringBuilder; StringBuffer synchronized và hiếm khi cần trong code hiện đại.
String pool không biến primitive thành object
String a = "abc";
String b = "ab" + "c";
String c = new String("abc");
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a.equals(c)); // true
System.out.println(a == c.intern()); // true
Contract: literal và string constant expression có interning theo quy tắc ngôn ngữ;
new String("abc") tạo object khác. intern() trả reference canonical;
nó không đổi identity của biến c. String literal vẫn là object String.
Metadata constant pool của class file và tập các object string được intern không phải cùng một khái niệm.
[String API] · [JLS 15] · [R03 · JVMS 2: Runtime areas]
Trade-off: pooling không phải lý do để dùng == so nội dung String
đến từ input. Không dựa vào integer wrapper cache để so logical value. Dùng key có equality ổn định;
việc map đã giữ reference không “đóng băng” state của key. StringBuilder phù hợp tích lũy trong loop;
một biểu thức nối chuỗi đơn giản không mặc nhiên cần viết lại thủ công.
7. Exception và quản lý tài nguyên
Checked exception phải được catch hoặc declare; unchecked bao gồm Error cùng các subclass, và RuntimeException cùng các subclass. Chọn exception theo khả năng caller xử lý có ý nghĩa, không theo thói quen. Không bắt rộng rồi bỏ qua; phải preserve cause khi translate exception.
Try-with-resources đóng resource theo thứ tự ngược lúc khai báo. Nếu body và close() cùng ném lỗi, lỗi từ body là primary, lỗi close nằm trong suppressed exceptions.
try (var reader = Files.newBufferedReader(path)) {
return reader.lines().toList();
} catch (IOException cause) {
throw new UncheckedIOException("Không đọc được " + path, cause);
}Unchecked còn bao gồm Error
Contract đã hiệu chỉnh: nhóm unchecked gồm RuntimeException,
Error và các subclass của chúng. catch (Exception e) không bắt
Error. Không biến điều này thành quy tắc bắt Throwable rồi tiếp tục;
nhiều Error phản ánh tình trạng ứng dụng không có khả năng phục hồi thông thường. [JLS 11]
import java.io.IOException;
public class ResourceFailureDemo {
static final class Resource implements AutoCloseable {
@Override
public void close() throws IOException {
throw new IOException("close");
}
}
public static void main(String[] args) {
try (var resource = new Resource()) {
throw new IOException("body");
} catch (IOException failure) {
System.out.println(failure.getMessage());
System.out.println(failure.getSuppressed()[0].getMessage());
}
}
}
Ví dụ đọc file nguồn là phần thân một method trả List<String> và nhận
Path path, với import từ java.io, java.nio.file.
Lỗi phát sinh trong traversal của reader.lines() có thể được bọc thành
UncheckedIOException, không đi vào riêng nhánh catch (IOException)
trong ví dụ. toList() giữ kết quả trong memory; file lớn cần chiến lược xử lý
streaming/bounded thay vì mặc định tải toàn bộ. [R16 · BufferedReader API]
Review rule: log nguyên cause và suppressed exceptions ở boundary chịu trách nhiệm;
không return/throw từ finally làm che mất lỗi gốc. Khi bắt
InterruptedException, truyền lên hoặc khôi phục interrupt flag theo contract của layer,
không nuốt tín hiệu dừng. [JLS 14] · [R09 · ExecutorService API]
8. Generics và type erasure
Generics đưa type check về compile time. Java generics invariant: List<Integer> không phải subtype của List<Number>. Wildcard mô tả họ kiểu: ? extends T phù hợp nguồn đọc T, ? super T phù hợp đích nhận T — quy tắc PECS: Producer Extends, Consumer Super.
Type parameter phần lớn bị erase khi compile để tương thích binary. Vì vậy không thể new T(), không thể tạo array generic trực tiếp và không thể dùng một giá trị kiểu Object để kiểm tra nội dung generic bằng instanceof List<String>. Compiler có thể sinh bridge method để giữ polymorphism sau erasure.
Wildcard là giới hạn phép toán, không phải phép đổi kiểu miễn phí
static <T> void copyAll(
java.util.List<? extends T> source,
java.util.List<? super T> target) {
for (T value : source) {
target.add(value);
}
}
Với ? extends T, đọc phần tử như T nhưng không thêm tùy ý một
T khác null vì kiểu cụ thể có thể hẹp hơn. Với ? super T, thêm
T được nhưng đọc chỉ chắc chắn là Object. Đây là quy tắc type safety,
không đảm bảo collection hỗ trợ mutation tại runtime; target không sửa được vẫn có thể ném
UnsupportedOperationException.
Hiệu chỉnh phát biểu instanceof: không thể từ một giá trị có kiểu
Object dùng instanceof List<String> để xác nhận kiểu của mọi phần tử.
Dùng List<?> để kiểm “là list”, rồi validate nội dung ở boundary. Tuy nhiên,
Java 16+ cho phép một số phép kiểm parameterized type đã an toàn từ static type, ví dụ
List<String> x với x instanceof ArrayList<String>.
Vì vậy không được diễn giải erasure thành “mọi instanceof có generic đều bị cấm”.
[JLS 15]
Trade-off: tránh raw type và unchecked cast khi có thể diễn đạt bằng generic signature. Compile với cảnh báo và kiểm boundary deserialize/input. Erasure không xóa toàn bộ metadata generic khỏi class file; vấn đề là runtime không kiểm từng type argument như một reified container contract.
9. Collections Framework
| Cấu trúc | Đặc tính chính | Dùng khi |
|---|---|---|
| ArrayList | Random access O(1), append amortized O(1), chèn/xóa giữa O(n). | Danh sách tổng quát, đọc/duyệt nhiều. |
| HashMap/HashSet | Tra cứu trung bình O(1), không đảm bảo thứ tự. | Lookup theo key hoặc uniqueness. |
| LinkedHashMap | Giữ insertion/access order. | Ordered map hoặc nền cho LRU. |
| TreeMap/TreeSet | Sorted, thao tác O(log n). | Cần range query hoặc thứ tự. |
| ArrayDeque | Thêm/xóa hai đầu hiệu quả. | Stack hoặc queue trong single-thread. |
| ConcurrentHashMap | Concurrent access, compound operation phải dùng API atomic. | Shared map nhiều thread. |
HashMap dùng hash để chọn bucket rồi dùng equals phân biệt key. Collision là bình thường. Capacity và load factor quyết định lúc resize. Từ Java 8, bucket quá đông có thể chuyển sang tree khi thỏa điều kiện implementation; không nên phụ thuộc các ngưỡng nội bộ như contract API.
Không sửa được chưa chắc là snapshot
var original = new java.util.ArrayList<>(java.util.List.of("A"));
var view = java.util.Collections.unmodifiableList(original);
var snapshot = java.util.List.copyOf(original);
original.add("B");
System.out.println(view); // [A, B]
System.out.println(snapshot); // [A]
Contract: unmodifiable view vẫn phản ánh backing collection; List.copyOf
không phản ánh những thay đổi cấu trúc sau đó của nguồn. Cả hai không tự deep-copy phần tử;
các factory List.of/copyOf không nhận phần tử null. [R06 · List API] · [R07 · Collections API]
Scope của bảng chọn cấu trúc: O(1) của hash lookup là điều kiện trung bình với
phân bố hash phù hợp, không phải latency SLO. Access order của LinkedHashMap cần
chế độ cấu hình tương ứng; map có order không tự trở thành cache có giới hạn hoặc thread-safe.
Tree ordering phải được chọn nhất quán với logical equality mong muốn.
Vận hành: fail-fast iterator của HashMap chỉ cố gắng phát hiện
structural modification, không phải cơ chế đồng bộ và không đảm bảo luôn ném
ConcurrentModificationException. Với shared state, chọn đúng collection và atomic
operation; không ghép containsKey rồi put và mặc nhiên cho là atomic.
[HashMap API]
10. Lambda, Stream và Optional
Lambda là implementation của functional interface — interface có một abstract method theo contract. Biến local được capture phải final hoặc effectively final. Stream mô tả pipeline xử lý dữ liệu; intermediate operation thường lazy, terminal operation kích hoạt traversal. Stream không phải collection và thường chỉ dùng một lần.
Map<String, Long> frequency = words.stream()
.map(word -> word.toLowerCase(Locale.ROOT))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));Parallel stream trong OpenJDK thường dùng common ForkJoinPool; đây không phải quyền chọn executor do Stream API bảo đảm, và parallel không mặc định nhanh hơn. Tránh với workload nhỏ, blocking I/O, shared mutable state hoặc môi trường cần kiểm soát executor. Optional thích hợp làm return type biểu diễn có/không; thường không nên dùng làm field entity, parameter phổ thông hoặc gọi get() thiếu kiểm tra.
Lazy execution không bảo đảm mọi lambda đều chạy
Ví dụ nguồn giả định List<String> words không chứa null và import
Map, Locale, Function, Collectors.
Với ["Java", "JAVA", "JVM"], kết quả đếm là java=2,
jvm=1; không suy ra thứ tự in map. Locale.ROOT biểu đạt chủ ý
normalize độc lập locale hiển thị, không phải giải pháp đầy đủ cho mọi quy tắc tìm kiếm ngôn ngữ.
Contract: behavioral parameter phải non-interfering và thường stateless;
stream implementation có thể lược bỏ operation nếu không làm đổi kết quả. Đừng đặt business
side effect bắt buộc trong peek/map rồi coi đó là guarantee. Một stream không được
traversal lại sau terminal operation. Captured reference effectively-final vẫn có thể trỏ tới object
mutable, do đó không tự giải quyết thread safety. [Stream API]
Scope implementation: common ForkJoinPool là hành vi thường gặp của parallel stream trong OpenJDK, không phải quyền chọn executor được Stream API bảo đảm. Với blocking I/O hoặc nhu cầu isolation, thiết kế executor riêng thay vì dựa vào mẹo phụ thuộc implementation. Đo sequential và parallel bằng cùng workload trước khi chọn.
Optional: ưu tiên biểu đạt absent/present ở return type. orElse
nhận một giá trị đã được tính theo quy tắc gọi method; orElseGet gọi supplier khi
cần giá trị thay thế. Vì vậy fallback tốn kém hoặc có side effect nên được cân nhắc riêng.
[Optional API] · [JLS 15]
11. Thread, synchronization và Java Memory Model
Race condition xuất hiện khi kết quả phụ thuộc interleaving không được kiểm soát. Ba khái niệm cần tách biệt: atomicity (thao tác không bị xen giữa), visibility (thread thấy write của thread khác), ordering (ràng buộc thứ tự quan sát).
synchronized cung cấp mutual exclusion và quan hệ happens-before khi unlock/lock cùng monitor. volatile cung cấp visibility và ordering cho read/write biến đó, nhưng compound action như count++ vẫn không atomic. Atomic classes cung cấp CAS-based atomic operations. Lock API phù hợp khi cần timed/interruptible acquisition, nhiều condition hoặc chiến lược lock nâng cao.
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();
ConcurrentHashMap<String, LongAdder> counters = new ConcurrentHashMap<>();
counters.computeIfAbsent("ok", key -> new LongAdder()).increment();Deadlock cần bốn điều kiện kinh điển; biện pháp thiết thực gồm global lock ordering, giảm phạm vi lock, tránh gọi external code khi giữ lock và dùng timeout khi phù hợp.
Visibility không biến read–modify–write thành atomic
Contract: unlock monitor happens-before lock sau đó trên cùng monitor;
write volatile happens-before các read sau đó của chính biến volatile ấy. Quan hệ này có thể
publish cả những write trước đó, không chỉ giá trị flag. Nó không khóa một chuỗi thao tác
read → tính → write như count++. Nhiều field cùng tạo một invariant
cần protocol bảo vệ chung. [JLS 17]
static final class Message {
private int payload;
private volatile boolean ready;
void publish() {
payload = 42;
ready = true;
}
int readWhenReady() {
return ready ? payload : -1;
}
}
Trong protocol một-lần ở trên, đọc ready == true cho phép quan sát
payload == 42; không được tổng quát hóa sang nhiều writer hoặc tái sử dụng
instance không có protocol mới. LongAdder trong ví dụ nguồn phù hợp counter
thống kê có contention: sum() không là atomic snapshot khi vẫn có update,
nên không dùng nó làm phép check-and-reserve quota. [R08 · LongAdder API]
Deadlock: bốn điều kiện kinh điển là mutual exclusion, hold-and-wait, no preemption và circular wait. Khi review, vẽ graph thứ tự lấy lock và external call. Timeout giúp thoát chờ theo policy, không tự bảo đảm dữ liệu đang sửa đã rollback hay không còn deadlock ở nơi khác.
12. Executor, CompletableFuture và virtual threads
ExecutorService tách việc submit task khỏi cách thread được quản lý. Luôn xác định lifecycle và shutdown. CompletableFuture mô tả graph xử lý bất đồng bộ: thenApply biến đổi value, thenCompose nối operation trả future, allOf phối hợp nhiều nhánh; phải thiết kế timeout, cancellation và exception path.
Virtual thread là lightweight Thread do JVM schedule, phù hợp workload có rất nhiều task chủ yếu chờ I/O. Nó giúp giữ code thread-per-request dễ đọc; không làm CPU chạy nhanh hơn và không loại bỏ giới hạn database connection hoặc downstream service. Không pool virtual thread; tạo mỗi task một thread và giới hạn tài nguyên khan hiếm bằng semaphore/pool/bulkhead.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> result = executor.submit(() -> callRemoteService());
System.out.println(result.get());
}Future hoàn tất không có nghĩa công việc nền đã dừng
Contract: các stage async của CompletableFuture không truyền
executor thường dùng common pool theo API; stage không có Async có thể chạy trên
thread hoàn tất stage. allOf trả CompletableFuture<Void>, không
tự trả list kết quả. orTimeout làm future hoàn tất lỗi khi quá hạn, không có contract
tự dừng I/O phía dưới; cancel(true) trên CompletableFuture cũng không bảo đảm
interrupt computation. Phải thiết kế deadline và cancellation ở client/task thực tế.
[CompletableFuture API]
Lifecycle: ví dụ nguồn giả định method bao quanh xử lý
InterruptedException, ExecutionException và có
callRemoteService(). get() đang chờ không giới hạn;
ExecutorService.close() chờ task hoàn tất. Vì vậy chỉ thêm timeout cho
get chưa bảo đảm toàn bộ block try-with-resources kết thúc đúng deadline.
[R09 · ExecutorService API]
synchronized có thể pin carrier. JEP 491 đã thay đổi cơ chế monitor ở
JDK 24, nên không sao chép nguyên lời khuyên “đổi mọi synchronized sang ReentrantLock”
cho mọi JDK. Một số đường native callback vẫn có thể pin; đo trên đúng JDK triển khai.
[JEP 444] · [R11 · JEP 491, JDK 24]Trade-off: virtual threads không làm CPU-bound task nhanh hơn. Tạo thread theo task nhưng vẫn giới hạn concurrency vào database/downstream. Với platform-thread executor, xác định pool size, queue bound và rejection policy; queue vô hạn có thể chuyển overload thành tăng memory và thời gian chờ. Cần đo in-flight task, queue wait, timeout, rejected task và downstream saturation, không chỉ thread count. [JEP 444] · [R10 · ThreadPoolExecutor API]
13. I/O, NIO.2, Date/Time và reflection
Path biểu diễn đường dẫn; Files cung cấp thao tác file. Ưu tiên API NIO.2 và try-with-resources cho stream tài nguyên. Không giả định separator, encoding hoặc case sensitivity giống nhau trên mọi hệ điều hành.
java.time cung cấp immutable, thread-safe types: Instant cho timestamp UTC, LocalDate cho ngày không timezone, ZonedDateTime cho thời điểm theo zone rules, Duration/Period cho khoảng thời gian. Lưu timestamp thường dùng Instant; chuyển timezone ở boundary hiển thị.
Reflection đọc type/member lúc runtime và có thể phá encapsulation, tăng coupling, giảm khả năng refactor/optimize. Framework dùng reflection có chủ đích; business code nên ưu tiên interface và explicit mapping.
Encoding, resource và timezone phải có chủ ý
Contract: stream từ Files.lines cần được đóng; dùng
try-with-resources, chọn charset theo hợp đồng dữ liệu và không mặc định đọc toàn bộ file lớn vào heap.
Đường dẫn và quyền truy cập thuộc môi trường triển khai; một đường dẫn hợp lệ trên máy phát triển
không phải guarantee trên máy đích. [R15 · Files API]
try (var lines = java.nio.file.Files.lines(
path, java.nio.charset.StandardCharsets.UTF_8)) {
long nonBlank = lines.filter(line -> !line.isBlank()).count();
System.out.println(nonBlank);
}
Date/time: Duration là lượng thời gian theo seconds/nanos;
Period là lượng theo ngày/tháng/năm trong lịch ISO. Cộng “một ngày lịch” vào
ZonedDateTime có thể khác cộng 24 giờ ở nơi đổi daylight saving time.
LocalDate không tự xác định một instant vì thiếu giờ và zone.
[R12 · Period API]
Reflection có giới hạn: setAccessible(true) không phải cách vượt
qua mọi module boundary. JPMS và việc package được opens cho caller ảnh hưởng
deep reflection; trySetAccessible() có thể trả false. Nếu framework cần mở package,
ghi lại phạm vi tối thiểu và kiểm bằng đúng chế độ deployment, không chỉ classpath trên IDE.
[R13 · AccessibleObject API]
14. Runtime memory, GC và chẩn đoán
Mỗi thread có stack chứa frame của method call; heap chứa object dùng chung. Trong HotSpot, Metaspace chứa class metadata trong native memory. JVM còn có code cache, thread stacks, direct buffers và native allocations, vì vậy process memory không chỉ là Java heap.
GC thu hồi object không còn reachable từ GC roots. “Java có GC” không đồng nghĩa không có memory leak: giữ reference ngoài ý muốn vẫn khiến object reachable. Các nguồn leak phổ biến gồm static collection, cache không giới hạn, listener không unregister, ThreadLocal không cleanup và queue tăng vô hạn.
| Triệu chứng | Bước kiểm tra đầu tiên |
|---|---|
| CPU cao | OS process/thread CPU, nhiều thread dump liên tiếp, JFR. |
| Heap tăng | GC log, class histogram, heap dump và retained size. |
| Treo/latency cao | Thread dump, lock contention, pool saturation, downstream latency. |
| Process memory cao nhưng heap ổn | Native Memory Tracking, direct buffer, thread count, metaspace. |
jcmd <pid> Thread.print
jcmd <pid> GC.class_histogram
jcmd <pid> JFR.start name=investigation duration=60s filename=app.jfrKhông suy ra process memory chỉ từ -Xmx
Scope: JVM specification mô tả runtime areas về mặt logic, không bắt mọi JVM dùng cùng layout. “Metaspace trong native memory” là mô hình HotSpot. Stack của virtual thread được triển khai bằng các stack chunk trong heap; không thể lấy số virtual thread nhân một native stack cố định để dự đoán memory. [R03 · JVMS 2: Runtime areas] · [JEP 444]
GC không phải lifecycle API: object không còn strong-reachable mới có cơ hội
được thu hồi theo cơ chế GC/reference processing; không có lời hứa thu hồi ngay tại cuối scope.
GC không thay close() cho file, connection hay resource bên ngoài.
Khi điều tra leak, tìm đường giữ reference và retained size, không chỉ tổng allocation.
GC.class_histogram được tài liệu
jcmd xếp impact cao tùy heap; heap dump cần capacity và kiểm soát truy cập vì có thể chứa dữ liệu
nhạy cảm. Thu thập trên đúng process với quyền được phép; cân nhắc ảnh hưởng latency trước khi chạy
trên production. [jcmd, Java 21]NMT: cần bật bằng startup option, ví dụ -XX:NativeMemoryTracking=summary.
Nó có overhead và chỉ theo dõi phạm vi memory của HotSpot, không phải mọi allocation native của
thư viện bên ngoài. Chọn baseline khi hệ thống đã ở trạng thái đại diện rồi so diff; đối chiếu với
OS memory thay vì coi NMT bằng RSS. [R14 · Native Memory Tracking]
jcmd <pid> VM.native_memory baseline
jcmd <pid> VM.native_memory summary.diff
jcmd <pid> Thread.dump_to_file -format=json threads.json
Với virtual threads, bổ sung dump theo lệnh Thread.dump_to_file thay vì coi
Thread.print truyền thống là toàn cảnh mọi virtual thread. Mỗi loại dump có mức thông tin
khác nhau; kết hợp JFR, GC log và dấu thời gian request để phân biệt CPU, lock, queue và downstream.
[JEP 444] · [jcmd, Java 21]
15. Chuyên đề mở rộng
Bốn chuyên đề dưới đây đào sâu những phần thường bị rút gọn trong tài liệu ôn phỏng vấn nhưng rất đáng học để hiểu Java như một platform hoàn chỉnh.
Class loading, bytecode & JPMS
Loading, linking, initialization, delegation, module graph, service loading và custom runtime image.
16 · Platform APII/O, networking, reflection & metadata
NIO.2, charset, serialization, HTTP Client, annotation, reflection, MethodHandle và proxy.
17 · Đồng thờiConcurrency nâng cao
Executor tuning, synchronizer, concurrent collections, Fork/Join, Flow, VarHandle và cancellation.
18 · JVMGC & production diagnostics
G1, ZGC, heap sizing, GC log, JFR, heap dump, native memory và quy trình điều tra.
Từ nền tảng sang chuyên đề, rồi quay lại thực hành
Bốn thẻ phía trên giữ nguyên phạm vi chuyên đề mở rộng của nguồn. Chọn Class loading khi cần giải thích lỗi liên kết hoặc module boundary; chọn Platform API khi vướng charset, resource, HTTP hay metadata; chọn Concurrency nâng cao khi cần executor/cancellation protocol; chọn GC & diagnostics khi phải đi từ triệu chứng vận hành tới bằng chứng runtime.
Những nhánh đã dẫn ở đầu bài và trong nội dung vẫn là phần của lộ trình: OOP design, String Pool, Collections architecture, Collections Internals, Sorting algorithms và Design Patterns.
Trang này có 15 nhóm kiến thức; con số 15–18 trên bốn thẻ là nhãn chuyên đề trong nguồn, không phải bốn nhóm bổ sung vào tổng 15 của trang. Điểm tự kiểm trong bài là phần bổ sung cho việc đọc lý thuyết, không thay hoặc cộng vào bộ câu hỏi/lab chính thức của Part 01.