Part 01 · Java Core · 1.2

Câu hỏi phỏng vấn

Hãy trả lời thành tiếng trước khi mở đáp án. Một câu trả lời Senior cần nêu cơ chế, điều kiện áp dụng, trade-off và ví dụ thực tế.

62 source entries11 nhóm chủ đề

Nền tảng ngôn ngữ

1. JDK, JVM và JRE khác nhau thế nào?
JVM thực thi bytecode và cung cấp runtime services. JDK là bộ phát triển gồm runtime cùng compiler và tooling. JRE là khái niệm môi trường chạy; từ Java 9 cách đóng gói module/runtime image khiến việc nói “cài JRE riêng” không còn chính xác như thời Java 8.
2. Java truyền object theo value hay reference?
Java luôn pass-by-value. Với object, giá trị được copy là reference. Method có thể mutation object mà reference cùng trỏ tới, nhưng gán parameter sang object khác không đổi biến của caller.

Follow-up: viết ví dụ phân biệt mutation và reassignment.

3. Field và local variable khác nhau về initialization?
Field nhận default value theo JLS. Local variable không tự có default value; compiler dùng definite-assignment analysis và từ chối đọc biến chưa chắc chắn được gán.
4. Interface khác abstract class ở quyết định thiết kế nào?
Interface phù hợp contract/capability và multiple inheritance of type. Abstract class phù hợp hierarchy cần chia sẻ state, constructor hoặc protected implementation. Quyết định dựa trên domain relationship, không chỉ dựa vào danh sách keyword.
5. Record có immutable hoàn toàn không?
Record làm component field final và không có setter sinh tự động, nhưng chỉ shallowly immutable. Nếu component là mutable list thì nội dung list vẫn đổi được. Muốn value object bất biến phải defensive copy ở constructor/accessor khi cần.

OOP và thiết kế object

OOP khác việc chỉ dùng class ở điểm nào?
OOP đặt state cùng behavior bảo vệ invariant, dùng contract để giảm coupling và cho phép polymorphism. Một class chỉ có getter/setter nhưng không bảo vệ quy tắc nghiệp vụ thường là data holder, chưa thể hiện nhiều lợi ích của object-oriented design.
Encapsulation khác abstraction?
Encapsulation kiểm soát quyền thấy/sửa state để giữ invariant. Abstraction cung cấp contract cần thiết và che chi tiết triển khai. Một PaymentGateway vừa abstract vendor API, vừa có thể encapsulate credential, retry state và validation.
Overloading khác overriding?
Overloading có cùng tên nhưng parameter list khác và được chọn lúc compile time từ static types. Overriding thay implementation của instance method trong subtype và được dispatch theo object runtime.

String Pool và concatenation

6. String Pool là gì?
Là cơ chế canonicalize String để literals/constant expressions có cùng nội dung có thể dùng chung instance. Logical equality vẫn dùng equals; == chỉ so identity và chỉ tình cờ true khi references cùng pooled object.
7. String literal khác new String?
Literal tham chiếu pooled canonical instance. new String("x") yêu cầu tạo String object riêng ngoài pooled literal, nên equals true nhưng == false. Cách new String thường không cần thiết.
8. intern() làm gì và có nên gọi thường xuyên?
intern trả canonical pooled reference có cùng nội dung, thêm vào pool nếu cần theo JVM semantics. Không dùng đại trà vì lookup/global table và cardinality cao có thể tăng CPU/memory; chỉ dùng khi đo được lợi ích và domain bounded.
9. Vì sao "ja" + "va" có thể == "java"?
Hai literals tạo constant expression được compiler fold thành "java" và intern. Nếu một operand chỉ biết ở runtime, kết quả thường là object mới; phải dùng equals. Final variable chỉ tham gia folding nếu nó là constant variable theo JLS.
10. String Pool còn nằm trong PermGen không?
Không với HotSpot hiện đại. Từ Java 7, interned String objects nằm trên heap; PermGen đã bị loại bỏ từ Java 8. Đây là JVM implementation history, không nên nói như language guarantee.

Equality, generics và collections

11. Vì sao override equals phải override hashCode?
Hash collection chọn vùng tìm kiếm bằng hash trước rồi mới kiểm tra equality. Contract yêu cầu object equal có cùng hash. Vi phạm khiến lookup/remove có thể thất bại dù logically equal.
12. HashMap tìm một key như thế nào?
Nó biến hash của key thành bucket index, tìm candidate trong bucket, rồi dùng identity/equality để xác nhận key. Collision được xử lý trong cùng bucket. Resize phân phối lại entry và có chi phí O(n), nên capacity/load factor đáng quan tâm với workload lớn.

Follow-up: key mutable gây lỗi gì? Treeification có phải API contract không?

13. ArrayList hay LinkedList?
ArrayList thường là mặc định vì locality tốt, random access O(1) và append amortized O(1). LinkedList chỉ có lợi trong một số thao tác tại node/đầu-cuối, nhưng tìm vị trí vẫn O(n) và allocation/cache locality thường kém. Nếu cần queue/deque, ưu tiên ArrayDeque.
14. Vì sao List<Integer> không phải List<Number>?
Generics invariant để giữ type safety. Nếu phép gán đó hợp lệ, ta có thể thêm Double qua reference List<Number> vào list vốn chỉ chứa Integer. Dùng wildcard extends/super theo hướng đọc/ghi.
15. ConcurrentHashMap có làm get rồi put atomic không?
Không. Từng operation có thread-safety, nhưng sequence nhiều operation không tự atomic. Dùng compute, merge, putIfAbsent hoặc synchronization phù hợp với invariant cần bảo vệ.

Kiến trúc Java Collections

16. Vẽ hierarchy chính của Collections Framework?
Iterable là gốc cho vòng lặp; Collection extends Iterable và có List, Set, Queue, Deque. Map thuộc framework nhưng không extends Collection vì lưu key-value mappings. Mỗi interface mô tả semantics, implementation quyết định cấu trúc dữ liệu, ordering, complexity và concurrency.
17. ArrayList khác LinkedList về kiến trúc?
ArrayList là mảng động: random access O(1), locality tốt, append amortized O(1), insert giữa O(n). LinkedList là doubly-linked nodes: tìm vị trí O(n), allocation/locality kém; add/remove chỉ O(1) khi đã có node/iterator. ArrayList thường là mặc định.
18. HashSet, LinkedHashSet và TreeSet khác nhau?
HashSet unique theo equals/hashCode và không đảm bảo order; LinkedHashSet giữ insertion order; TreeSet sorted/navigable theo comparator với O(log n). Comparator trả 0 xác định duplicate trong TreeSet nên cần nhất quán với equals khi semantics yêu cầu.
19. HashMap, LinkedHashMap và TreeMap chọn thế nào?
HashMap cho lookup tổng quát average O(1); LinkedHashMap khi cần insertion/access order; TreeMap khi cần sorted keys, range, floor/ceiling với O(log n). Chọn theo semantics trước, rồi mới complexity.
20. Queue, Deque và PriorityQueue khác nhau?
Queue mô hình head/tail; Deque thao tác hai đầu và ArrayDeque phù hợp cả FIFO lẫn stack. PriorityQueue là heap, chỉ đảm bảo head có priority cao/nhỏ nhất; iteration không sorted. offer/poll/peek trả special value, còn add/remove/element có thể ném exception.
21. Vì sao Map không extends Collection?
Collection biểu diễn nhóm phần tử E với add/remove/contains. Map biểu diễn mapping K sang V, key unique và API như put/get không khớp contract phần tử đơn. keySet, values và entrySet cung cấp Collection views khi cần.
22. Unmodifiable collection có immutable không?
Không nhất thiết. Collections.unmodifiableList là read-only view; backing list vẫn có thể đổi. List.copyOf tạo unmodifiable snapshot nông, nhưng object phần tử vẫn có thể mutable. Deep immutability cần kiểm soát cả elements.
23. Chọn concurrent collection theo tiêu chí nào?
ConcurrentHashMap cho shared key-value và atomic APIs; CopyOnWriteArrayList cho read/iteration nhiều, write rất ít; ConcurrentLinkedQueue cho non-blocking unbounded queue; bounded BlockingQueue cho producer-consumer cần backpressure. Thread-safe operation không tự bảo vệ compound invariant nhiều cấu trúc.

Stream và concurrency

24. Stream lazy có ích gì?
Intermediate operations chỉ mô tả pipeline; terminal operation mới kéo dữ liệu qua. JVM/library có thể fuse operations, short-circuit và tránh intermediate collection. Lazy không có nghĩa kết quả được cache.
25. Parallel stream có luôn nhanh hơn không?
Không. Nó có overhead chia/gộp, dùng common pool, phụ thuộc kích thước dữ liệu, khả năng split và chi phí mỗi phần tử. Blocking I/O hoặc shared mutable state có thể làm chậm hay sai. Chỉ dùng sau khi benchmark trong workload đại diện.
26. volatile có làm count++ thread-safe không?
volatile đảm bảo visibility và một số ordering, nhưng count++ là read-modify-write gồm nhiều bước nên vẫn race. Dùng AtomicInteger, LongAdder hoặc lock tùy invariant và contention.
27. synchronized, Lock và atomic khác nhau thế nào?
synchronized đơn giản, tự release và cung cấp monitor happens-before. Lock cho timed/interruptible acquisition và condition linh hoạt nhưng phải unlock trong finally. Atomic phù hợp state transition nhỏ dùng CAS; không thay thế lock cho invariant nhiều biến.
28. Phòng deadlock bằng cách nào?
Đặt global lock ordering, giữ lock ngắn, tránh nested lock/call ngoại vi khi giữ lock, dùng tryLock timeout khi phù hợp và quan sát bằng thread dump. Cần bảo vệ invariant chứ không chữa bằng sleep.
29. thenApply khác thenCompose?
thenApply(T -> U) biến đổi value. Nếu function trả CompletionStage, thenApply tạo nested future; thenCompose(T -> CompletionStage<U>) flatten và nối async workflow, tương tự map và flatMap.
30. Virtual thread giải quyết vấn đề gì?
Nó làm mô hình thread-per-request scale tốt hơn cho workload nhiều blocking I/O, giữ code tuần tự dễ đọc. Nó không tăng tốc CPU-bound work và không tăng capacity database/downstream. Giới hạn tài nguyên khan hiếm vẫn bắt buộc.

JVM và production

31. Java có memory leak không?
Có. GC chỉ thu hồi object unreachable. Nếu ứng dụng vô tình giữ reference qua static collection, cache, listener, ThreadLocal hoặc queue, object vẫn reachable và heap tiếp tục tăng.
32. Heap cao và process memory cao có giống nhau?
Không. Process còn có metaspace, code cache, thread stack, direct/native buffer và JVM native allocation. Heap ổn nhưng RSS tăng cần kiểm tra native memory, số thread và direct buffer.
33. Ứng dụng treo nhưng CPU thấp: bắt đầu ở đâu?
Lấy nhiều thread dump cách nhau vài giây; tìm blocked/waiting threads, deadlock, pool saturation và cùng stack lặp lại. Đồng thời kiểm tra downstream latency, connection pool, queue và GC pause. Không kết luận từ một snapshot duy nhất.

Câu hỏi thiết kế cấp Senior

34. Thiết kế bộ đếm concurrent có throughput cao

Nếu chỉ cần tổng cuối cùng và contention cao, cân nhắc LongAdder. Nếu cần giá trị atomic chính xác tại mỗi read/update, AtomicLong phù hợp hơn. Nếu update liên quan nhiều field/invariant, dùng lock hoặc thiết kế state immutable + atomic replacement. Đưa ra benchmark và semantics trước khi chọn.

35. Thiết kế cache local bounded và thread-safe

Phải làm rõ eviction policy, TTL, concurrency, memory bound, stampede và observability. Không nên tự ghép ConcurrentHashMap với queue rồi tuyên bố LRU chính xác nếu chưa bảo vệ compound invariant. Production thường dùng Caffeine; custom lab chỉ để hiểu cơ chế.

Câu hỏi chuyên đề mở rộng

36. Khi nào class được initialize, và load có đồng nghĩa initialize không?
Không. Loading tạo Class từ binary representation; initialization chạy static initializer khi có active use theo JLS. Class.forName có overload cho phép chọn có initialize hay không.
37. ClassNotFoundException khác NoClassDefFoundError?
ClassNotFoundException đến từ thao tác load theo tên không tìm thấy class. NoClassDefFoundError là linkage error khi JVM cần definition nhưng không có, hoặc initialization trước đó thất bại.
38. exports khác opens trong JPMS?
exports cho truy cập public API bình thường. opens cho deep reflection. Framework reflection thường cần opens; không nên mở toàn module nếu chỉ cần một package.
39. Tại sao deserialize dữ liệu không tin cậy nguy hiểm?
Native serialization có thể dựng object graph và kích hoạt gadget trước business validation. Không deserialize input không tin cậy; nếu bắt buộc, dùng allow-list filter và boundary cô lập.
40. Reader khác InputStream ở đâu?
InputStream đọc byte; Reader đọc character sau decoding. Chuyển byte thành text phải có charset rõ ràng.
41. LongAdder tốt hơn AtomicLong khi nào?
LongAdder tăng throughput cho counter bị contention nhưng sum không phải atomic snapshot. AtomicLong phù hợp khi cần một linearizable value hoặc CAS transition.
42. Vì sao unbounded executor queue nguy hiểm?
Khi producer nhanh hơn consumer, queue che overload bằng latency và memory tăng đến OOM. Bounded queue cùng rejection/backpressure biểu lộ saturation sớm.
43. G1, Parallel GC và ZGC khác nhau theo mục tiêu nào?
Parallel ưu tiên throughput; G1 cân bằng throughput và pause; ZGC ưu tiên pause thấp. Chọn theo SLO, heap, CPU và workload rồi đo.
44. Heap ổn nhưng RSS tăng: giả thuyết nào?
Thread stack, direct buffer, metaspace/class loader, code cache, JNI hoặc native allocation. Thu thập thread count, NMT và OS memory map.
45. Heap histogram có đủ chứng minh memory leak?
Không. Histogram chỉ cho count/shallow bytes. Cần xu hướng và retention path từ heap dump/dominator tree.

Câu hỏi nền tảng bổ sung

46. HashMap, TreeMap và LinkedHashSet khác nhau thế nào?
HashMapTreeMap lưu key-value; LinkedHashSet chỉ lưu phần tử unique nên không phải một loại Map. HashMap lookup trung bình O(1), không bảo đảm thứ tự; TreeMap sắp key và hỗ trợ range/floor/ceiling với O(log n); LinkedHashSet kiểm tra unique trung bình O(1) và giữ insertion order. Chọn theo data model và ordering/range requirement, không chỉ theo complexity.
47. Functional Interface khác Anonymous Inner Class thế nào?
Functional interface có đúng một abstract method và có thể được hiện thực bằng lambda, method reference hoặc class thường. Lambda ngắn gọn, dùng target typing và this của scope bên ngoài; anonymous class tạo một object với class scope riêng, this trỏ tới instance đó và có thể khai báo thêm state/method. Cả hai chỉ capture local variable final/effectively final; dùng lambda cho hành vi SAM ngắn, anonymous class khi thật sự cần identity hoặc state riêng.
48. Stream API khác vòng lặp truyền thống khi nào?
Stream mô tả pipeline khai báo, lazy cho intermediate operations và chỉ chạy khi có terminal operation; phù hợp transform/filter/aggregate, dễ composition và có thể parallel khi workload đáp ứng. Vòng lặp phù hợp khi cần control flow phức tạp, mutation có chủ đích, nhiều early exit hoặc debug từng bước. Stream không tự nhanh hơn, chỉ dùng một lần và side effect trong pipeline làm code khó đúng, đặc biệt với parallel stream.
49. Garbage Collector hoạt động thế nào và chọn collector dựa trên gì?
GC bắt đầu từ GC roots, đánh dấu object còn reachable rồi thu hồi/compact vùng không còn reachable; vì vậy nó quản lý memory chứ không đóng mọi resource và không ngăn leak do reference bị giữ. Generational collectors tận dụng phần lớn object sống ngắn; một số phase vẫn stop-the-world dù collector concurrent. Chọn theo throughput, pause SLO, heap, CPU và workload; theo dõi allocation rate, pause, promotion, live set rồi benchmark trước khi tuning.

Follow-up theo tình huống

50. Insert hoặc remove ở giữa có làm LinkedList nhanh hơn ArrayList không?
Chỉ khi đã có sẵn node hoặc iterator tại đúng vị trí thì LinkedList unlink/link O(1). Nếu bắt đầu từ index, nó vẫn phải traverse O(n); ArrayList phải dịch phần tử O(n) nhưng copy memory liên tục thường rất nhanh. Hãy benchmark với kích thước, vị trí và access pattern thực tế thay vì kết luận từ Big O của riêng thao tác unlink.
51. Vì sao ArrayList thường iterate nhanh và tốn ít memory hơn LinkedList?
ArrayList lưu references liên tục nên tận dụng CPU cache/prefetch và chỉ có overhead của backing array. LinkedList tạo một node object cho mỗi phần tử, giữ prev/next references, tăng allocation, pointer chasing và GC pressure. LinkedList chỉ hợp một số queue/deque hoặc splice-like access; trong Java, ArrayDeque thường tốt hơn cho hai đầu.
52. Điều gì xảy ra khi dùng mutable object làm key của HashMap?
Nếu field tham gia equals/hashCode đổi sau khi put, key có thể nằm ở bucket theo hash cũ nhưng lookup dùng hash mới nên không tìm thấy hoặc invariant duplicate bị phá. Key nên immutable trong thời gian nằm trong map. Với TreeMap, thay field dùng bởi comparator cũng có thể phá ordering/search tree semantics.
53. Khi nào LinkedHashSet phù hợp hơn HashSet hoặc TreeSet?
Dùng LinkedHashSet khi cần loại duplicate nhưng vẫn trả kết quả theo insertion order, ví dụ deduplicate input mà giữ thứ tự đầu tiên. Nó có lookup trung bình O(1) nhưng tốn thêm liên kết để duy trì order. TreeSet phù hợp khi cần sorted/range operations O(log n); HashSet khi không cần order và muốn overhead thấp hơn.
54. @FunctionalInterface kiểm tra điều gì? Default/static method có làm mất tính functional không?
Annotation yêu cầu compiler xác nhận interface có đúng một abstract method theo SAM rules; methods override-equivalent với Object không được tính. Interface vẫn có thể có nhiều default, static và private methods. Annotation không bắt buộc để dùng lambda nhưng giúp phát hiện refactor vô tình thêm abstract method thứ hai.
55. Lambda và anonymous class khác nhau thế nào khi overload hoặc ném checked exception?
Lambda phụ thuộc target type nên overload nhiều functional interfaces có cùng shape có thể ambiguous và cần cast/type rõ. Checked exception của lambda phải tương thích với throws trên SAM; functional interface chuẩn như Function không cho checked exception tùy ý. Anonymous class verbose hơn nhưng có class body/identity riêng và có thể chọn interface/class contract rõ ràng.
56. Lazy evaluation và short-circuit giúp Stream xử lý dữ liệu lớn thế nào?
Intermediate operations không tạo kết quả ngay; terminal operation kéo từng phần tử qua pipeline. Các operation như findFirst, anyMatch hoặc limit có thể dừng sớm, kể cả với stream rất lớn hoặc vô hạn nếu pipeline/order cho phép. Tuy nhiên stateful operations như sorted thường phải buffer toàn bộ input trước.
57. Vì sao reduce/collect có thể sai khi chuyển sang parallel stream?
Accumulator/combiner phải associative, identity phải trung lập và pipeline phải non-interfering. Mutate cùng một ArrayList hoặc counter từ nhiều worker tạo race; một combiner sai có thể chỉ lộ lỗi khi parallel. Dùng collector phù hợp, immutable reduction hoặc concurrent collector có contract rõ và chỉ parallel sau benchmark.
58. Young GC, old/full collection khác nhau ở tín hiệu và nguyên nhân nào?
Young collection thường do vùng cấp phát trẻ đầy và chủ yếu xử lý object sống ngắn. Old-generation pressure xuất hiện khi live set/promotion tăng, concurrent cycle không theo kịp hoặc allocation đặc biệt; full collection thường đắt hơn nhưng tên/trigger cụ thể tùy collector. Đọc GC log theo allocation rate, pause, promoted bytes, occupancy và cause thay vì chỉ đếm số lần GC.
59. Strong, Soft, Weak và Phantom reference dùng khi nào?
Strong reference giữ object reachable bình thường. Weak reference phù hợp metadata/canonical mapping không được giữ key sống, như WeakHashMap; SoftReference bị thu hồi theo memory pressure nhưng timing khó dự đoán nên không phải cache production tốt. PhantomReference kết hợp ReferenceQueue để theo dõi hậu-mortem/cleanup native resource; resource quan trọng vẫn nên đóng deterministic bằng try-with-resources/Cleaner phù hợp.
← Lý thuyếtTiếp: Thực hành →