伪共享是多线程高频修改逻辑独立但同属一个64字节缓存行的变量时,因MESI协议频繁失效缓存行导致性能骤降的现象,典型表现为吞吐量下降50%以上、L1缓存失效率>15%、火焰图中出现大量__lll_lock_wait。

并行流在处理大规模变量更新时性能突然变差,不是代码写错了,而是CPU缓存底层在“拖后腿”。伪共享不会报错、不抛异常,只悄悄把吞吐量拉低一半甚至更多——尤其当多个线程高频修改逻辑独立但内存相邻的变量时。
确认是否真由伪共享引发
先排除其他瓶颈,再聚焦伪共享:
- 用 Async-Profiler 或 perf 抓取
L1-dcache-load-misses和cache-references,若失效率 >15%,高度可疑 - 观察火焰图中大量时间耗在
__lll_lock_wait或memmove类函数,实际并无显式锁,说明缓存同步开销已主导执行路径 - 做对照实验:把原结构中两个被并发写的字段(如
volatile long a, b)拆到两个独立对象里,性能若显著回升(常见2–8倍),基本锁定伪共享
定位伪共享发生的内存位置
Java 中变量布局受JVM优化影响,不能仅凭源码顺序判断是否同缓存行:
- 用 jol-core 工具打印对象内存布局:
GraphLayout.parseInstance(obj).toPrintable() - 重点关注字段偏移量(
OFFSET):若两个volatile long字段地址差 ≤64 字节(x86主流缓存行大小),且都被高频写入,就极可能共享缓存行 - 注意:JVM默认字段重排序可能让
a和b更靠近;@Contended注解或手动填充后的偏移应 ≥64 字节间隔
并行流场景下的典型伪共享模式
Stream.parallel() 底层走 ForkJoinPool.commonPool(),任务分片后常出现以下高危结构:
- 自定义累加器类中含多个
volatile计数器(如successCount/failCount),未隔离 - 使用
LongAdder但误将其多个cells数组元素分配在连续内存中,导致线程争抢同一缓存行 - Map/Reduce 中 key-value 对象复用频繁,且对象内字段紧凑排列(如
int id; long ts; boolean flag;被多个线程同时更新)
针对性修复与验证
不靠猜测,靠实测闭环:
- 启用
-XX:-RestrictContended -XX:ContendedPaddingWidth=64,对关键字段加@Contended,重启后重跑 JMH 基准测试 - 若无法改JVM参数,采用手动填充:每个需独占缓存行的字段后紧跟7个
long(56字节),凑满64字节对齐 - 验证修复效果:不仅看总耗时,还要比对
perf stat -e cache-misses,instructions的 miss ratio 是否下降至


















