JVM thread ↔ OS thread

4 câu hỏi~25 phútTừ khóa: platform thread, 1:1, LWP, TID, /proc/PID/task, top -H, nid, jstack, -Xss, thread stack, unable to create native thread, pids.max, virtual thread, carrier thread

Tiếp đi: thread của Java và thread của Linux. ps chỉ thấy đúng một process java, nhưng top -H lại hiện cả trăm dòng "java". Một new Thread() trong code thì Linux thấy cái gì?

Bài 3.1 đã nói một process java có nhiều thread. Bài này trả lời: mỗi thread đó là gì trong kernel, và dùng điều đó để chẩn đoán.

1. Bài toán

ps -o pid,nlwp,cmd -p $(pgrep -f order/app.jar)
  PID NLWP CMD
 4123  214 java -Xmx1g -jar /opt/order/app.jar

214 thread. Sau một đợt tăng traffic, log báo:

java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached

Heap vẫn còn trống. Vậy "memory" nào bị hết?

2. Vấn đề

3. Giải pháp: mô hình 1:1

4. Code từng bước

Bước 1: Thấy thread trong /proc

PID=$(pgrep -f order/app.jar)
ls /proc/$PID/task | wc -l                    # 214
cat /proc/$PID/task/*/comm | sort | uniq -c | sort -rn | head
    200 http-nio-8080-e     ← tên thread bị cắt còn 15 ký tự
      4 GC Thread#0..3
      2 C2 CompilerThre
      1 VM Thread
      1 HikariPool-1 ho

JVM đặt tên OS thread theo tên Java thread, nên đặt tên pool rõ ràng giúp chẩn đoán từ phía Linux:

@Bean
ThreadPoolTaskExecutor invoiceExecutor() {
    var ex = new ThreadPoolTaskExecutor();
    ex.setThreadNamePrefix("invoice-");   // ngắn, hiện đủ trong top -H
    ex.setCorePoolSize(4);
    ex.setMaxPoolSize(8);
    ex.setQueueCapacity(500);
    return ex;
}

Bước 2: Nối OS thread với Java thread

top -H -p $PID -n 1 | head -12
  PID USER   %CPU COMMAND
 4188 order  97.0 invoice-3
printf '%x\n' 4188           # 105c
jcmd $PID Thread.print | grep -A12 'nid=0x105c'
"invoice-3" #61 prio=5 os_prio=0 tid=0x00007f... nid=0x105c runnable
   java.lang.Thread.State: RUNNABLE
     at java.util.regex.Pattern$Loop.match(...)

Quy trình đầy đủ khi CPU 100% (nhiều dump, flame graph) ở 2.6.

Bước 3: Kiểm tra giới hạn tạo thread

grep -E 'Max processes' /proc/$PID/limits      # ulimit -u (đếm theo user, gồm cả thread)
cat /proc/sys/kernel/threads-max
cat /proc/sys/kernel/pid_max
systemctl show order-service -p TasksMax        # systemd giới hạn task qua cgroup pids
cat /sys/fs/cgroup/system.slice/order-service.service/pids.current
[Service]
TasksMax=4096

Bước 4: Chỉnh stack size khi cần

-Xss512k                         # cho mọi thread
new Thread(null, task, "deep-parser", 4 * 1024 * 1024).start();   // riêng thread cần stack sâu

Giảm -Xss không phải cách chính. Cách chính là giới hạn pool, hoặc dùng virtual thread cho tác vụ chờ I/O.

5. Cách hoạt động

Platform thread (1:1)
 Java:   Thread "http-nio-8080-exec-1"   Thread "invoice-3"   Thread "GC Thread#0"
            │                               │                    │
 JVM:    pthread_create ──────────────── clone() ────────────────┘
            ▼                               ▼                    ▼
 Kernel: task TID 4130                task TID 4188         task TID 4125
         (stack 1MB reserve)          (nid=0x105c)
            └──────── cùng PID 4123: chung heap, chung fd ────────┘
                   Linux scheduler chọn task nào lên core

Virtual thread (M:N)
 VT#1 VT#2 VT#3 ... VT#10000   (object trên heap, stack lưu ở heap khi park)
    \   |   /        |
     mount/unmount
  carrier "ForkJoinPool-1-worker-1..N"  (N ≈ số core, là platform thread)
            ▼
 Kernel: chỉ thấy N task

6. So sánh

Platform threadVirtual thread
Linux thấyMột task mỗi threadChỉ thấy carrier thread
Hiện trong top -HCó, đúng tênKhông; CPU tính cho ForkJoinPool-1-worker-N
StackReserve theo -Xss, ngoài heapLưu trên heap, co giãn
Giới hạn số lượngulimit -u, pids.max, RAMChủ yếu là heap
Thread dumpjstack/jcmd Thread.printjcmd PID Thread.dump_to_file -format=json file.json
Ai scheduleKernelJVM (lên carrier), rồi kernel (carrier lên core)

7. Khi nào nên dùng / không nên dùng

Vậy Java thread priority (setPriority(10)) có làm Linux cho thread đó chạy nhiều hơn không?

Bạn đoán hợp lý, nhưng trên Linux câu trả lời là gần như không.

Không nên dựa vào thread priority để đảm bảo đúng logic hay hiệu năng.

Với virtual thread, nếu top -H thấy ForkJoinPool-1-worker-2 ăn 100% CPU thì làm sao biết request nào gây ra?

Đúng là khó hơn, vì carrier chỉ là "xe chở". Virtual thread nào đang ngồi trên carrier thì thay đổi liên tục.

jcmd $PID Thread.dump_to_file -format=json /tmp/vt.json
# tìm virtual thread đang RUNNABLE, xem stack của nó
jq '.threadDump.threadContainers[].threads[] | select(.state=="RUNNABLE") | .stack[0:5]' /tmp/vt.json

Phỏng vấn hỏi Java thread và OS thread quan hệ thế nào thì trả lời sao?

Q1. Java thread map với OS thread thế nào?

🇻🇳 Tiếng Việt:

Platform thread là 1:1: mỗi Java thread là một OS thread, kernel schedule, có TID riêng và stack riêng theo -Xss. Virtual thread là M:N: nhiều virtual thread chạy trên một số ít carrier thread, kernel chỉ thấy carrier.

🇬🇧 English:

Platform threads are one-to-one: each Java thread is an OS thread with its own TID and an -Xss sized stack, scheduled by the kernel. Virtual threads are many-to-many: many virtual threads run on a small number of carrier threads, and the kernel only sees the carriers.

Q2. Tìm thread Java đang ăn CPU trên Linux thế nào?

🇻🇳 Tiếng Việt:

Em dùng top -H -p PID để thấy TID tốn CPU, đổi sang hex, rồi tìm nid đó trong jcmd Thread.print. nid chính là TID của Linux. Đặt tên thread pool rõ ràng giúp nhận ra ngay từ top -H.

🇬🇧 English:

I run top -H -p PID to find the TID using CPU, convert it to hex and search for that nid in jcmd Thread.print. The nid is the Linux TID. Clear thread pool names make it recognizable directly in top -H.

Q3. "unable to create native thread" nghĩa là gì?

🇻🇳 Tiếng Việt:

JVM xin kernel tạo OS thread mới và bị từ chối, không phải hết heap. Em kiểm tra số thread hiện tại, ulimit -u, pids.max hoặc TasksMax của cgroup, và bộ nhớ cho stack. Thường gốc rễ là pool không giới hạn hoặc rò rỉ thread, nên sửa code trước rồi mới nâng limit.

🇬🇧 English:

The JVM asked the kernel for a new OS thread and was refused; it is not about the heap. I check the current thread count, ulimit -u, the cgroup pids.max or TasksMax, and memory for stacks. Usually the root cause is an unbounded pool or a thread leak, so I fix the code before raising limits.

Cách nhớ ngắn gọn


Tự kiểm tra

Thread dump ghi nid=0x105c. Trong top -H tìm số nào?

0x105c = 4188, là TID trong top -H.

Ở đâu trong /proc thấy các thread của process?

/proc/PID/task/, mỗi thư mục con là một TID.

"unable to create native thread" khi heap còn trống, kiểm tra gì?

Số thread, ulimit -u, pids.max/TasksMax, bộ nhớ cho stack.

10.000 virtual thread thì ls /proc/PID/task | wc -l tăng 10.000?

Không. Kernel chỉ thấy carrier thread (khoảng số core) cộng các thread khác của JVM.

setPriority(10) có tác dụng trên Linux mặc định không?

Không, HotSpot bỏ qua priority trên Linux mặc định (os_prio=0).