Java 系统缓存

缓存操作

CPU缓存


内存对于CPU来说还是太慢,进而发展出L1,L2,L3三级缓存
L1缓存最接近CPU,存储容量小但是速度最快

速度参照

  • L1缓存1ns
  • L2缓存3ns
  • L3缓存15ns
  • 内存60ns

https://github.com/colin-scott/interactive_latencies

CPU首选缓存操作。如果写操作时,缓存存在就是写命中,否则是写缺失,层层缓存寻找,都没有的情况下去内存加载

CPU缓存通信


L1,L2一般是CPU核专属缓存,L3是核共享缓存
专属缓存相对独立的同时产生了不一致问题,因此缓存间需要一套通信机制

MESI协议规定了缓存状态

  • M(Modified): 和内存不一致,已修改
  • E(Exclusive):和内存一致,其他核没有
  • S(Shared):和内存一致,其他核也有
  • I(Invalid):失效
    写操作发送RFO(Request For Owner)请求,获得权限后其他缓存副本设为失效

伪共享(false sharing)


多核状态下多个线程操作同一对象,对象内表面看逻辑上没关系的两个字段,其实在执行时可能存在缓存行竞争
CPU以缓存行为单位,即块操作,常见大小是64字节
如果两个独立变量xy碰巧位于同一缓存行,一个CPU修改x,另一CPU修改y,导致两个CPU都加载了同一缓存行
那么任意修改xy都会造成另一CPU对应的缓存行失效,那么就要发送RFO请求进行状态控制,造成了竞争

解决方案

编程时可以刻意定义多余无用的成员变量进行填补,避免特定变量间使用同一缓存行
Java8增加了标签@sun.misc.Contended,设置JVM参数启用-XX:-RestrictContended
注解使得被标记字段单独占用一个缓存行,虽然浪费空间但是避免竞争提升了效率

应用

Striped64类使用数组减少竞争,是LongAdder的基类
数组容易连续分配,造成缓存伪共享,因此加上@Contended标签进行优化

Striped64.java
1
2
3
4
5
6
7
8
9
10
abstract class Striped64 extends Number {
@sun.misc.Contended
static final class Cell {
volatile long value;
//...
}

transient volatile Cell[] cells;
//...
}