[{"data":1,"prerenderedAt":553},["ShallowReactive",2],{"\u002Fjava-interview\u002Foperating-systems":3,"java-interview-search:\u002Fjava-interview\u002Foperating-systems":508},{"id":4,"title":5,"body":6,"category":494,"cover":495,"date":496,"description":497,"extension":498,"group":499,"meta":500,"navigation":501,"order":502,"path":503,"seo":504,"sitemap":505,"stem":506,"__hash__":507},"javaInterview\u002Fjava-interview\u002Foperating-systems.md","操作系统：进程、IO 模型与容器资源",{"type":7,"value":8,"toc":480},"minimark",[9,25,118,123,126,129,132,135,139,142,145,148,154,158,161,164,171,174,178,192,207,214,227,231,234,245,252,275,279,310,321,324,327,331,334,341,359,362,366,373,380,391,394,397,417,437,440],[10,11,12,13,17,18,17,21,24],"p",{},"Java 后端的操作系统题通常带着「线上」两个字：不是让你背内核源码，而是能不能用 ",[14,15,16],"code",{},"top","、",[14,19,20],{},"vmstat",[14,22,23],{},"iostat"," 解释现象，并知道 JVM 与容器的边界。",[26,27,28,44],"table",{},[29,30,31],"thead",{},[32,33,34,38,41],"tr",{},[35,36,37],"th",{},"现象",[35,39,40],{},"先看什么",[35,42,43],{},"常见原因",[45,46,47,64,85,99],"tbody",{},[32,48,49,53,61],{},[50,51,52],"td",{},"CPU 高",[50,54,55,17,57,60],{},[14,56,16],{},[14,58,59],{},"top -Hp","、线程栈",[50,62,63],{},"死循环、GC、正则回溯、加密计算",[32,65,66,69,82],{},[50,67,68],{},"负载高但 CPU 不高",[50,70,71,73,74,77,78,81],{},[14,72,20],{}," 的 ",[14,75,76],{},"r"," 和 ",[14,79,80],{},"b"," 列",[50,83,84],{},"IO 等待、不可中断睡眠",[32,86,87,90,96],{},[50,88,89],{},"内存涨",[50,91,92,95],{},[14,93,94],{},"free","、RSS、cgroup 指标",[50,97,98],{},"堆外内存、page cache、线程栈",[32,100,101,104,115],{},[50,102,103],{},"磁盘慢",[50,105,106,73,109,17,112],{},[14,107,108],{},"iostat -x",[14,110,111],{},"await",[14,113,114],{},"%util",[50,116,117],{},"随机 IO、fsync 频繁、磁盘打满",[119,120,122],"h2",{"id":121},"_1-进程线程协程有什么区别","1. 进程、线程、协程有什么区别？",[10,124,125],{},"进程是资源分配的基本单位，拥有独立地址空间、文件描述符和信号处理；线程是调度的基本单位，同一进程内的线程共享堆和方法区，各自有栈和寄存器。",[10,127,128],{},"线程切换要进入内核，保存和恢复上下文，还可能刷新 TLB、影响缓存局部性，成本通常是微秒级。进程切换还要切换地址空间，开销更大。",[10,130,131],{},"协程是用户态的轻量执行单元，由运行时调度，切换不经过内核，成本低。它适合大量阻塞在 IO 上的任务，但不等于「免费」：阻塞系统调用仍会占用底层线程，CPU 密集任务也不会因此变快。",[10,133,134],{},"Java 21 的虚拟线程由 JVM 调度到载体线程上，遇到阻塞 IO 会自动让出，本质上是把协程模型引入 JVM。注意它不能突破数据库连接池、下游限流等外部瓶颈。",[119,136,138],{"id":137},"_2-虚拟内存是怎么工作的","2. 虚拟内存是怎么工作的？",[10,140,141],{},"每个进程看到的是独立的虚拟地址空间，通过页表映射到物理内存。CPU 访问地址时由 MMU 查表，TLB 缓存最近的映射，避免每次都走多级页表。",[10,143,144],{},"内存按页管理，通常 4KB。访问未映射或未加载的页会触发缺页中断：如果是合法映射但数据不在内存，就从磁盘或 swap 调入；如果是非法访问，进程收到段错误。",[10,146,147],{},"这种按需加载让进程可以申请超过物理内存的地址空间，也让多个进程共享同一份文件页。文件读写默认走 page cache，写操作先改缓存，变成脏页后由内核回写。",[10,149,150,151,153],{},"问题在于，",[14,152,94],{}," 显示的可用内存很少不一定代表内存不足，page cache 会在压力下回收。真正要关注的是可用内存、swap 使用和 OOM 事件。",[119,155,157],{"id":156},"_3-五种-io-模型怎么理解epoll-为什么高效","3. 五种 IO 模型怎么理解？epoll 为什么高效？",[10,159,160],{},"阻塞 IO 在等待数据和拷贝数据两个阶段都阻塞；非阻塞 IO 轮询数据是否就绪，就绪后拷贝仍阻塞；IO 多路复用在单个线程里等待多个 fd 就绪；信号驱动在就绪时收到信号；异步 IO 连数据拷贝完成后再通知。",[10,162,163],{},"select 用位图传递 fd，有数量上限，返回后要遍历全部 fd；poll 用数组去掉了上限，但仍需遍历。epoll 把 fd 注册到内核的红黑树，就绪的 fd 放入就绪链表，调用时只返回就绪项。",[10,165,166,167,170],{},"epoll 支持水平触发和边缘触发。水平触发只要还有数据就会持续通知，编程简单；边缘触发只在状态变化时通知一次，必须一次读到 ",[14,168,169],{},"EAGAIN","，通常配合非阻塞 fd，效率更高但更容易写错。",[10,172,173],{},"Reactor 模式就是把 epoll 的事件循环与业务处理解耦，Netty、Redis、Nginx 都是这个思路。",[119,175,177],{"id":176},"_4-cpu-使用率高怎么排查","4. CPU 使用率高怎么排查？",[10,179,180,181,183,184,187,188,191],{},"先用 ",[14,182,16],{}," 看是用户态还是内核态高：",[14,185,186],{},"us"," 高多半是业务计算、GC 或死循环；",[14,189,190],{},"sy"," 高可能是系统调用、锁竞争或上下文切换过多。",[10,193,194,195,198,199,202,203,206],{},"找到进程后，",[14,196,197],{},"top -Hp \u003Cpid>"," 定位到具体线程，把线程 id 转成十六进制，再在 ",[14,200,201],{},"jstack"," 输出里按 ",[14,204,205],{},"nid"," 搜索，就能看到对应栈。Java 进程还可以用 JFR 或 async-profiler 生成火焰图，直接看热点方法。",[10,208,209,210,213],{},"GC 也会表现为 CPU 高：用 ",[14,211,212],{},"jstat -gcutil"," 看回收频率和耗时，若 Full GC 频繁，问题在内存而不是计算。",[10,215,216,217,73,219,221,222,73,224,226],{},"负载高但 CPU 不高时，看 ",[14,218,20],{},[14,220,80],{}," 列和 ",[14,223,23],{},[14,225,111],{},"，可能是 IO 阻塞或大量不可中断睡眠。别只盯着 CPU 使用率一个指标。",[119,228,230],{"id":229},"_5-内存不足时系统会发生什么","5. 内存不足时系统会发生什么？",[10,232,233],{},"当物理内存和可回收的 page cache 都不足时，内核会尝试回收、压缩，必要时使用 swap。swap 频繁换入换出会导致系统抖动，延迟急剧上升。",[10,235,236,237,240,241,244],{},"如果仍然无法满足，Linux 的 OOM Killer 会按评分选择进程杀掉，容器里则可能表现为 ",[14,238,239],{},"OOMKilled","、退出码 137。这类问题不会在 Java 堆 dump 里留下明显证据，要看 ",[14,242,243],{},"dmesg"," 和 cgroup 的内存事件。",[10,246,247,248,251],{},"Java 进程的内存不只是堆：还有元空间、直接内存、线程栈、JIT 代码缓存和本地库。容器内存限制下，堆要预留这些空间，不能简单地把 ",[14,249,250],{},"-Xmx"," 设成容器上限。",[10,253,254,255,17,258,17,261,17,264,267,268,77,271,274],{},"排查用 ",[14,256,257],{},"free -h",[14,259,260],{},"\u002Fproc\u002Fmeminfo",[14,262,263],{},"smem",[14,265,266],{},"jcmd VM.native_memory","，以及容器的 ",[14,269,270],{},"memory.current",[14,272,273],{},"memory.max","。",[119,276,278],{"id":277},"_6-磁盘-io-慢怎么定位","6. 磁盘 IO 慢怎么定位？",[10,280,281,282,285,286,17,289,292,293,17,296,299,300,302,303,305,306,309],{},"先看 ",[14,283,284],{},"iostat -x 1","：",[14,287,288],{},"r\u002Fs",[14,290,291],{},"w\u002Fs"," 是 IOPS，",[14,294,295],{},"rkB\u002Fs",[14,297,298],{},"wkB\u002Fs"," 是吞吐，",[14,301,111],{}," 是平均等待时间，",[14,304,114],{}," 表示设备忙碌程度，",[14,307,308],{},"aqu-sz"," 反映排队深度。",[10,311,312,314,315,317,318,320],{},[14,313,111],{}," 高而 ",[14,316,114],{}," 低，可能是单次 IO 很大或底层存储延迟高；",[14,319,114],{}," 接近 100% 且队列长，说明设备已经饱和。",[10,322,323],{},"再看是顺序还是随机：数据库和日志写入的随机 IO 对机械盘很不友好，SSD 的随机性能好得多。文件系统、RAID 和云盘的 IOPS 上限也要纳入考虑。",[10,325,326],{},"应用层要区分「读写量大」和「fsync 频繁」。每次写都同步刷盘会放大延迟，批量提交和合理的刷盘策略往往比换硬件更有效。",[119,328,330],{"id":329},"_7-死锁的四个条件和处理方式","7. 死锁的四个条件和处理方式？",[10,332,333],{},"死锁需要互斥、持有并等待、不可抢占、循环等待同时成立。破坏任意一个就能避免，但工程上最常用的是固定加锁顺序和超时重试。",[10,335,336,337,340],{},"不可抢占意味着已经拿到的锁不能被强制收回，所以「按顺序申请，按逆序释放」能消除循环等待；",[14,338,339],{},"tryLock"," 带超时可以让线程在拿不到锁时退出，避免无限等待。",[10,342,343,344,347,348,350,351,354,355,358],{},"检测方面，Java 用 ",[14,345,346],{},"jcmd Thread.print"," 或 ",[14,349,201],{},"，输出会标出相互等待的线程；数据库用 ",[14,352,353],{},"SHOW ENGINE INNODB STATUS","；操作系统层面可以看 ",[14,356,357],{},"\u002Fproc\u002F\u003Cpid>\u002Fstack"," 和等待链。",[10,360,361],{},"还要区分死锁、活锁和饥饿：活锁是线程一直在重试但无法推进，饥饿是低优先级线程长期拿不到资源。",[119,363,365],{"id":364},"_8-容器里的-jvm-要注意什么","8. 容器里的 JVM 要注意什么？",[10,367,368,369,372],{},"容器用 namespace 做隔离、cgroup 做资源限制。JDK 10 起 ",[14,370,371],{},"UseContainerSupport"," 默认开启，JVM 会读取 cgroup 的 CPU 和内存限制，而不是宿主机总量；更早的版本会误判，必须显式设置。",[10,374,375,376,379],{},"内存方面，堆上限可以用 ",[14,377,378],{},"-XX:MaxRAMPercentage"," 按容器内存比例设置，但要给元空间、直接内存、线程栈和 JIT 留出空间。设置过满时，容器 OOM Killer 会直接杀掉进程，而不是抛 Java 堆 OOM。",[10,381,382,383,386,387,390],{},"CPU 方面，",[14,384,385],{},"-XX:ActiveProcessorCount"," 会影响 GC 线程数和默认并行度；",[14,388,389],{},"cpu.max"," 的配额可能导致 JVM 看到的核数与实际可用算力不一致，线程池要按实际配额压测。",[10,392,393],{},"还要注意 cgroup v1 与 v2 的指标路径不同，监控采集和 JVM 版本都要确认支持。",[119,395,396],{"id":396},"易错点与自测",[398,399,400,404,409],"ul",{},[401,402,403],"li",{},"协程不是「更快的线程」，它优化的是阻塞等待的调度成本。",[401,405,406,408],{},[14,407,94],{}," 少不等于内存不足，page cache 可回收；容器 OOM 与 Java 堆 OOM 是两件事。",[401,410,411,414,415,274],{},[14,412,413],{},"epoll"," 高效的前提是 fd 已注册且事件就绪，边缘触发下必须读到 ",[14,416,169],{},[10,418,419,420,422,423,425,426,428,429,17,431,77,434,436],{},"自测：① 说明 ",[14,421,111],{}," 高但 ",[14,424,114],{}," 低的可能原因；② 解释容器内存限制下 ",[14,427,250],{}," 为什么要留余量；③ 用 ",[14,430,16],{},[14,432,433],{},"printf",[14,435,201],{}," 串起一次 CPU 高排查。",[119,438,439],{"id":439},"参考资料",[398,441,442,452,460,472],{},[401,443,444,451],{},[445,446,450],"a",{"href":447,"rel":448},"https:\u002F\u002Fman7.org\u002Flinux\u002Fman-pages\u002Fman7\u002Fepoll.7.html",[449],"nofollow","Linux man-pages：epoll"," — 水平\u002F边缘触发与就绪模型",[401,453,454,459],{},[445,455,458],{"href":456,"rel":457},"https:\u002F\u002Fman7.org\u002Flinux\u002Fman-pages\u002Fman5\u002Fproc.5.html",[449],"Linux man-pages：proc"," — 进程状态、内存与线程信息",[401,461,462,467,468,471],{},[445,463,466],{"href":464,"rel":465},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F21\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Flang\u002FRuntime.html#availableProcessors()",[449],"Java SE 21：容器感知"," — ",[14,469,470],{},"availableProcessors"," 与容器 CPU 限制",[401,473,474,479],{},[445,475,478],{"href":476,"rel":477},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F444",[449],"JEP 444：Virtual Threads"," — 虚拟线程与平台线程的调度差异",{"title":481,"searchDepth":482,"depth":482,"links":483},"",2,[484,485,486,487,488,489,490,491,492,493],{"id":121,"depth":482,"text":122},{"id":137,"depth":482,"text":138},{"id":156,"depth":482,"text":157},{"id":176,"depth":482,"text":177},{"id":229,"depth":482,"text":230},{"id":277,"depth":482,"text":278},{"id":329,"depth":482,"text":330},{"id":364,"depth":482,"text":365},{"id":396,"depth":482,"text":396},{"id":439,"depth":482,"text":439},"java-interview",null,"2026-10-24","梳理进程线程协程、虚拟内存、epoll 与 CPU\u002F内存\u002F磁盘排查方法，并补上容器环境下 JVM 的资源边界","md","分布式与工程",{},true,"140","\u002Fjava-interview\u002Foperating-systems",{"title":5,"description":497},{"loc":503},"java-interview\u002Foperating-systems","a9B0punXurF67zwHa0wPISIavhF0-zSgCuNLz5UIGVE",[509,513,517,521,525,529,533,537,541,545,549],{"id":503,"title":5,"titles":510,"content":511,"level":512},[],"梳理进程线程协程、虚拟内存、epoll 与 CPU\u002F内存\u002F磁盘排查方法，并补上容器环境下 JVM 的资源边界 Java 后端的操作系统题通常带着「线上」两个字：不是让你背内核源码，而是能不能用 top、vmstat、iostat 解释现象，并知道 JVM 与容器的边界。 现象 先看什么 常见原因 CPU 高 top、top -Hp、线程栈 死循环、GC、正则回溯、加密计算 负载高但 CPU 不高 vmstat 的 r 和 b 列 IO 等待、不可中断睡眠 内存涨 free、RSS、cgroup 指标 堆外内存、page cache、线程栈 磁盘慢 iostat -x 的 await、%util 随机 IO、fsync 频繁、磁盘打满",1,{"id":514,"title":122,"titles":515,"content":516,"level":482},"\u002Fjava-interview\u002Foperating-systems#_1-进程线程协程有什么区别",[5],"进程是资源分配的基本单位，拥有独立地址空间、文件描述符和信号处理；线程是调度的基本单位，同一进程内的线程共享堆和方法区，各自有栈和寄存器。 线程切换要进入内核，保存和恢复上下文，还可能刷新 TLB、影响缓存局部性，成本通常是微秒级。进程切换还要切换地址空间，开销更大。 协程是用户态的轻量执行单元，由运行时调度，切换不经过内核，成本低。它适合大量阻塞在 IO 上的任务，但不等于「免费」：阻塞系统调用仍会占用底层线程，CPU 密集任务也不会因此变快。 Java 21 的虚拟线程由 JVM 调度到载体线程上，遇到阻塞 IO 会自动让出，本质上是把协程模型引入 JVM。注意它不能突破数据库连接池、下游限流等外部瓶颈。",{"id":518,"title":138,"titles":519,"content":520,"level":482},"\u002Fjava-interview\u002Foperating-systems#_2-虚拟内存是怎么工作的",[5],"每个进程看到的是独立的虚拟地址空间，通过页表映射到物理内存。CPU 访问地址时由 MMU 查表，TLB 缓存最近的映射，避免每次都走多级页表。 内存按页管理，通常 4KB。访问未映射或未加载的页会触发缺页中断：如果是合法映射但数据不在内存，就从磁盘或 swap 调入；如果是非法访问，进程收到段错误。 这种按需加载让进程可以申请超过物理内存的地址空间，也让多个进程共享同一份文件页。文件读写默认走 page cache，写操作先改缓存，变成脏页后由内核回写。 问题在于，free 显示的可用内存很少不一定代表内存不足，page cache 会在压力下回收。真正要关注的是可用内存、swap 使用和 OOM 事件。",{"id":522,"title":157,"titles":523,"content":524,"level":482},"\u002Fjava-interview\u002Foperating-systems#_3-五种-io-模型怎么理解epoll-为什么高效",[5],"阻塞 IO 在等待数据和拷贝数据两个阶段都阻塞；非阻塞 IO 轮询数据是否就绪，就绪后拷贝仍阻塞；IO 多路复用在单个线程里等待多个 fd 就绪；信号驱动在就绪时收到信号；异步 IO 连数据拷贝完成后再通知。 select 用位图传递 fd，有数量上限，返回后要遍历全部 fd；poll 用数组去掉了上限，但仍需遍历。epoll 把 fd 注册到内核的红黑树，就绪的 fd 放入就绪链表，调用时只返回就绪项。 epoll 支持水平触发和边缘触发。水平触发只要还有数据就会持续通知，编程简单；边缘触发只在状态变化时通知一次，必须一次读到 EAGAIN，通常配合非阻塞 fd，效率更高但更容易写错。 Reactor 模式就是把 epoll 的事件循环与业务处理解耦，Netty、Redis、Nginx 都是这个思路。",{"id":526,"title":177,"titles":527,"content":528,"level":482},"\u002Fjava-interview\u002Foperating-systems#_4-cpu-使用率高怎么排查",[5],"先用 top 看是用户态还是内核态高：us 高多半是业务计算、GC 或死循环；sy 高可能是系统调用、锁竞争或上下文切换过多。 找到进程后，top -Hp \u003Cpid> 定位到具体线程，把线程 id 转成十六进制，再在 jstack 输出里按 nid 搜索，就能看到对应栈。Java 进程还可以用 JFR 或 async-profiler 生成火焰图，直接看热点方法。 GC 也会表现为 CPU 高：用 jstat -gcutil 看回收频率和耗时，若 Full GC 频繁，问题在内存而不是计算。 负载高但 CPU 不高时，看 vmstat 的 b 列和 iostat 的 await，可能是 IO 阻塞或大量不可中断睡眠。别只盯着 CPU 使用率一个指标。",{"id":530,"title":230,"titles":531,"content":532,"level":482},"\u002Fjava-interview\u002Foperating-systems#_5-内存不足时系统会发生什么",[5],"当物理内存和可回收的 page cache 都不足时，内核会尝试回收、压缩，必要时使用 swap。swap 频繁换入换出会导致系统抖动，延迟急剧上升。 如果仍然无法满足，Linux 的 OOM Killer 会按评分选择进程杀掉，容器里则可能表现为 OOMKilled、退出码 137。这类问题不会在 Java 堆 dump 里留下明显证据，要看 dmesg 和 cgroup 的内存事件。 Java 进程的内存不只是堆：还有元空间、直接内存、线程栈、JIT 代码缓存和本地库。容器内存限制下，堆要预留这些空间，不能简单地把 -Xmx 设成容器上限。 排查用 free -h、\u002Fproc\u002Fmeminfo、smem、jcmd VM.native_memory，以及容器的 memory.current 和 memory.max。",{"id":534,"title":278,"titles":535,"content":536,"level":482},"\u002Fjava-interview\u002Foperating-systems#_6-磁盘-io-慢怎么定位",[5],"先看 iostat -x 1：r\u002Fs、w\u002Fs 是 IOPS，rkB\u002Fs、wkB\u002Fs 是吞吐，await 是平均等待时间，%util 表示设备忙碌程度，aqu-sz 反映排队深度。 await 高而 %util 低，可能是单次 IO 很大或底层存储延迟高；%util 接近 100% 且队列长，说明设备已经饱和。 再看是顺序还是随机：数据库和日志写入的随机 IO 对机械盘很不友好，SSD 的随机性能好得多。文件系统、RAID 和云盘的 IOPS 上限也要纳入考虑。 应用层要区分「读写量大」和「fsync 频繁」。每次写都同步刷盘会放大延迟，批量提交和合理的刷盘策略往往比换硬件更有效。",{"id":538,"title":330,"titles":539,"content":540,"level":482},"\u002Fjava-interview\u002Foperating-systems#_7-死锁的四个条件和处理方式",[5],"死锁需要互斥、持有并等待、不可抢占、循环等待同时成立。破坏任意一个就能避免，但工程上最常用的是固定加锁顺序和超时重试。 不可抢占意味着已经拿到的锁不能被强制收回，所以「按顺序申请，按逆序释放」能消除循环等待；tryLock 带超时可以让线程在拿不到锁时退出，避免无限等待。 检测方面，Java 用 jcmd Thread.print 或 jstack，输出会标出相互等待的线程；数据库用 SHOW ENGINE INNODB STATUS；操作系统层面可以看 \u002Fproc\u002F\u003Cpid>\u002Fstack 和等待链。 还要区分死锁、活锁和饥饿：活锁是线程一直在重试但无法推进，饥饿是低优先级线程长期拿不到资源。",{"id":542,"title":365,"titles":543,"content":544,"level":482},"\u002Fjava-interview\u002Foperating-systems#_8-容器里的-jvm-要注意什么",[5],"容器用 namespace 做隔离、cgroup 做资源限制。JDK 10 起 UseContainerSupport 默认开启，JVM 会读取 cgroup 的 CPU 和内存限制，而不是宿主机总量；更早的版本会误判，必须显式设置。 内存方面，堆上限可以用 -XX:MaxRAMPercentage 按容器内存比例设置，但要给元空间、直接内存、线程栈和 JIT 留出空间。设置过满时，容器 OOM Killer 会直接杀掉进程，而不是抛 Java 堆 OOM。 CPU 方面，-XX:ActiveProcessorCount 会影响 GC 线程数和默认并行度；cpu.max 的配额可能导致 JVM 看到的核数与实际可用算力不一致，线程池要按实际配额压测。 还要注意 cgroup v1 与 v2 的指标路径不同，监控采集和 JVM 版本都要确认支持。",{"id":546,"title":396,"titles":547,"content":548,"level":482},"\u002Fjava-interview\u002Foperating-systems#易错点与自测",[5],"协程不是「更快的线程」，它优化的是阻塞等待的调度成本。 free 少不等于内存不足，page cache 可回收；容器 OOM 与 Java 堆 OOM 是两件事。 epoll 高效的前提是 fd 已注册且事件就绪，边缘触发下必须读到 EAGAIN。 自测：① 说明 await 高但 %util 低的可能原因；② 解释容器内存限制下 -Xmx 为什么要留余量；③ 用 top、printf 和 jstack 串起一次 CPU 高排查。",{"id":550,"title":439,"titles":551,"content":552,"level":482},"\u002Fjava-interview\u002Foperating-systems#参考资料",[5],"Linux man-pages：epoll — 水平\u002F边缘触发与就绪模型 Linux man-pages：proc — 进程状态、内存与线程信息 Java SE 21：容器感知 — availableProcessors 与容器 CPU 限制 JEP 444：Virtual Threads — 虚拟线程与平台线程的调度差异",1793263560017]