cpu读取数据的顺序:cpu->寄存器->1级缓存->2级缓存->3级缓存(3级缓存开始是CPU共享的)->主内存->硬盘,读取数据不是一个对象或者一个字节为单位读取的,而已一缓存行为单位,CPU 不同缓存行的大小也不同,一般为32-256个字节,最常见的缓存行大小是64个字节。如果一个缓存行存了多个对象,两个cpu 分别都加载了其中的一个对象( 比如cpu1 加载 了A,cpu2 加载了B ),然后如果cpu1频繁的修改A,那么cpu2即便只是读取B对象,会因为cpu1修改了A对象,使A对象所在的缓存行失效,cpu2就需要频繁的从3级缓存中加载失效的缓存行。上面描述的不是必须竞争修改,cpu1,cpu2 没有同时修改同一份数据,这种不以必要但是又真是存在的缓存行失效问题叫做伪内存共享。
填充缓存行,使整个Java对象占满整个缓存行。不同Java版本不一样,比如开启指针压缩的情况下,对象头标记8字节,对象类型指针4字节就是12 字节,如果我们只有一个long 类型的字段(8字节),然后Java对象是8字节对齐的,我们填充5个long 类型(40),这时候就是60字节,然后由padding 区域填满剩下的4字节。这样就占了整个缓存行。
JDK7中填充空的属性部分版本会失效,这时候可以通过在父类中添加属性来填充。
JDK8以后官方给出解决缓存行填充的注解@Contended,来使对象填充缓存行。但是默认情况加了也无效,需要 指定jvm 参数,使它生效需要同时开启 JVM 参数:-XX:-RestrictContended=false(这个参数的意思禁用Java类库以外的缓存行填充,这个参数默认是true),如果想Java自带类库缓存行填充都禁用 –XX: EnableContented=false就可以(默认是true)。
另外在说说,缓存行填充不应该随意使用,除非你确定你需要它,测试,普通Java对象填充和不填充效果基本没区别(没区别是应为出现的概率比较小,并不是真的没区别),但是被volatile修饰的对象效率 差10倍,所以volatile的对象可能更加需要填充。volatile关键字也不应该乱用,用了它会大大的降低效率,除非你真的需要它带来的内存可见性和禁用指令重排序。