这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
《Java 并发编程·性能诊断与事故》9.1 过期读与无限循环
从停不掉的停机标志出发,构造非 volatile 标志导致的无限循环,用线程转储、JFR 采样与 PrintCompilation 定位到 C2 把字段读提升到循环外,并在本机用 30 次独立 JVM 运行逐档对照 Xint、C1、C2 与 volatile 修复的复现率。
posts
这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
从停不掉的停机标志出发,构造非 volatile 标志导致的无限循环,用线程转储、JFR 采样与 PrintCompilation 定位到 C2 把字段读提升到循环外,并在本机用 30 次独立 JVM 运行逐档对照 Xint、C1、C2 与 volatile 修复的复现率。
从构造的负库存现象出发,给出把 1024 并发缩到 2 个 actor、库存缩到 1 的四步最小化路径;用本机两轮 JCStress 命中率与 StressSeed 说明概率复现的边界,并给出可阻断 CI 的回归测试、Maven profile 与 nightly 成本预算。
从库存扣减的读改写解释线性化点,用 Lincheck 的模型检验系统地枚举线程交错;给出 Kotlin + Maven 的最小工程、场景生成参数与失败报告读法,并如实标注本机未安装 Kotlin 运行时、2.44 在 Maven Central 不存在(最新 2.39),全部结果未在本机执行。
从库存扣减的读改写竞态出发,拆解 JCStress 0.16 的 @JCStressTest、@State、@Actor、@Arbiter、@Outcome 与 Expect 分工,给出自定义结果类型的完整示例;并在本机 JDK 25 复现 proc=full 缺失导致的 TestList 空指针,再实跑两轮记录 FORBIDDEN 命中率。
事件循环、虚拟线程、平台线程池同时存在时,事故往往发生在边界上:阻塞调用混进循环、上下文在换线程时丢失、jstack 看不到未挂载的虚拟线程。本节用本机实测标出三种边界与 trace 断点,并给出线程转储、JFR 事件与边界策略的具体判读方法。
把事件循环与虚拟线程放在团队熟悉度、调试难度、生态、阻塞调用、尾延迟、内存、可观测性七个维度上逐条比较,每维给出哪边更好及其原因;用同一批 600 个混合任务在本机四次量出 P99 差距(5.2 秒对 0.26 秒),并给出一张填好权重就能出结论的选型表与可运行的打分程序。
用一个 80 行以内的 JDK Selector 事件循环讲清 Reactor 的两个数据结构、非阻塞前提与背压本质,并用本机实测展示一次阻塞调用如何把 3 个并发请求的往返延迟从 5 ms 拉到约 50 ms,以及停止读取后 TCP 窗口如何在 2.9 MB 处关死。
从作者删帖三天后搜索仍能看到摘要的事故写起,说明删除必须留下墓碑而不能等索引重建。文章用 Lag 与 Stale 判断索引水位是否落后预算,用 Retry 保持失败重试顺序,并给出时间倒退、零预算与墓碑重试的测试、真实运行输出和一致性窗口的产品口径。
虚拟线程放开了线程数,却把瓶颈推到连接池、ThreadLocal 与日志上下文上。本节实测 5000 个虚拟线程撞上 8 个连接时 p99 涨到 16.7 秒,量出每线程一份 ThreadLocal 的堆代价,并用可运行代码演示 MDC 串号与三种修法。
JDK 24 的 JEP 491 让 synchronized 不再 pin 载体线程,旧教材的清单已经过期。本节在本机把十种阻塞写法逐个实测,给出类初始化、Object.wait、native 帧三类仍然会 pin 的场景与 JFR 事件原文,并说明为什么阈值必须降到 0ms。
十万和一百万虚拟线程在本机实跑:三档创建耗时 49/179/597 ms,堆占用约 1 KB 一条,载体线程始终 10 个。文章给出完整数据、781 MB 的 jcmd 转储实测,并解释 ThreadMXBean 与 Thread.print 为何完全看不见虚拟线程。
队列变长并不会降低拒绝率,只会把拒绝推迟并把尾延迟抬高。本节用开环定速压测跑出 4 与 16 两种池大小、0/16/64/256 四种队列长度的拒绝率与 P99,对照 AbortPolicy、CallerRunsPolicy、DiscardPolicy 在过载下的真实行为,并给出一套带拒绝计数与降级返回值的入口实现。
同样是「加线程」,CPU 密集任务在 10 线程后吞吐就不再上涨,IO 密集任务到 48 线程仍在涨。本节讲清 N+1 与 N×(1+W/C) 的来历,用 ThreadMXBean 的线程 CPU 时间实测 W/C,在本机十核上跑出两条完整的池大小曲线,并说明虚拟线程时代公式的落点如何转移到连接池。
线程池该开多大,不该靠手感。本节从 L = λW 的计数推导讲起,用一次开环定速到达的实验校核「系统内请求数」与「吞吐乘延迟」是否相等,给出算所需并发、反推吞吐上限、判断余量三种用法,以及一张从假设到结论的容量估算表,并说明稳态、有界、不区分分位这三个前提。
把锁顺序规范落成可执行的规则:按业务 ID 排序、按类型分层、粗粒度到细粒度单向升级,并给出 tryLock(timeout) 兜底的转账代码与真实代价——采样 60 份转储发现 10 份存在反向边,固定顺序后 20 份降为 0,附可直接运行的检测脚本。
用可运行的重试碰撞实验复现活锁,给出 2 秒内 31 万到 38 万个回合、成功 0 次的真实计数,对照非公平锁与公平锁下写者的等待分位与吞吐差距,实测 macOS 上 Thread.setPriority 的 os_prio 不变、迭代数无稳定差异,最后给出带抖动的退避与锁选择建议。
把死锁的四个必要条件逐条映射到 ReentrantLock 转账代码的具体行,用一份真实的 jcmd Thread.print -l 转储确认 JVM 报出的 ownable synchronizer 环,并给出基于 ThreadMXBean.findDeadlockedThreads() 的看门狗代码、真实输出与连续两次确认的防抖策略。
用一份同时包含 BLOCKED、WAITING (parking) 与 Java 级死锁的真实线程转储,讲清 jcmd Thread.print -l 与 jstack -l、- waiting to lock/- locked/parking to wait for 的配对方法,如何从地址找到锁持有者,以及 Found one Java-level deadlock 段的读法与修复后的验证。
从一次搜索结果里出现无权限标题、点进去 403 的事故写起,拆开召回、排序与过滤三个阶段的职责。文章用 Match 纯函数把作者、时间、删除标记与多词命中写成可测试的布尔判断,给出边界用例、真实运行输出,并解释清楚过滤为什么必须发生在排序之前。
async-profiler 本机未安装,本节只给 macOS 与 release 包的安装方式、-e lock/-e alloc/-d/-o 命令和火焰图读法,并明确不贴它的输出;随后用 JFR 的 jdk.ObjectAllocationSample 实跑一次分配采样,给出 allocation-by-class/site 与 weight 字段的真实数据,形成可复现的替代证据。