最近看了一本书,因为以前不太了解底层原理,所以这块比较薄弱,所以通过本文做下记录和总结。
在计算机系统中,内存是以【缓存行】为单位存储的,一个缓存行存储的字节是2的倍数。不同机器上,缓存行大小也不一样,通常来说为64字节。
伪共享是指:在多个线程同时读写同一个【缓存行】上的不同数据时,尽管这些变量之间没有任何关系,但是在多线程之间仍然需要同步,从而导致性能下降。在多核处理器中,伪共享是影响性能的主要因素之一,通常称之为:“性能杀手”。
可能上述文字阐述的不是很直白,可以通过这个图理解下:
线程a 在CPU1上读写变量X, 同时线程b上读写变量Y,但是巧合的是变量 X , Y 在同一个缓存行上,那边线程a,b就要互相竞争获取该缓存行的读写权限,才可以进行读写。
假如 线程a在内核1上 获取了缓存行的读写权限,进行了操作,就会导致其他内核中的x变量和y变量同时失效,那么线程b 就必须刷新它的缓存后才能在内核2上获取缓存行的读写权限。 这就导致了这个缓存行在不同的线程之间多次通过 L3缓存进行交换最新复制的数据,极大的影响了CPU性能,如果CPU不在同一个槽上,性能更糟糕。
Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。
所有java 对象都有8字节的对象头,前4个字节用于保存对象的哈希码(前3个字节)和对象锁状态(后一个字节),假如对象处于上锁状态,这4个字节都会被拿到对象外,并用指针进行连接。
剩下的4个字节用来存储对象所属类的引用。
另外,对于数组而言,还有一个保存数组大小的变量,也是4个字节。
对象的实例就是我们在Java对象中看到的属性及其值。
JVM 要求Java的对象占用的内存大小必须是8bit的整数倍,所以后面有几个字节用于把对象的大小补齐值8bit 倍数。
每个java 对象都会对齐到8字节的倍数,不够会进行填充,为了保证效率,Java 编译器通过字段类型进行了排序:
顺序 | 类型 | 字节数量(字节) |
---|---|---|
1 | double | 8 |
2 | long | 8 |
3 | int | 4 |
4 | float | 4 |
5 | Short | 2 |
6 | char | 2 |
7 | boolean | 1 |
8 | byte | 1 |
9 | 对象引用 | 4或者8 |
10 | 子类字段 | 重新排序 |
这里采用书本的样例,可以直接运行,可以调整线程数量 来观摩对性能的影响。
/**
* @author zhanghuilong
* @desc Java中缓存行,伪共享的理解
* @since 2019/06/12
*/
public class FalseSharingDemo {
// 测试使用线程数
private final static int NUM_THREADS= 4;
// 测试次数
private final static int NUM_TEST_TIMES= 10;
// 无填充 无缓存行对齐的对象类 普通热变量
static class PlainHotVariable {
// 1个long 类型变量 占用内存 1*8 = 8 字节,
public volatile long value = 0L;
}
// 有填充 有缓存行对其的对象类
static class AlignHotVariable extends PlainHotVariable{
// 用于填充,6个long 类型的变量 ,总占用内存为 6*8 = 48 字节
// 加上继承父类的一个变量value,那么总共该对象 占用内存为:8字节对象头 + 8字节父类变量+ 6*8字节填充变量 = 64字节,
// 正好满足一个对象全部在一个缓存行中,消除了伪竞争问题
public long p1,p2,p3,p4,p5,p6;
}
// 竞争者
static final class CompetitorThread extends Thread {
//迭代次数
private final static long ITERATIONS = 500L * 1000L * 1000L;
private PlainHotVariable plainHotVariable;
public CompetitorThread(final PlainHotVariable plainHotVariable) {
this.plainHotVariable = plainHotVariable;
}
@Override
public void run() {
for (int i=0;i t2;
}
public static void runOneSuit(int threadsNum, int testNum) throws Exception{
int expectedCount = 0;
for (int i = 0; i < testNum; i++) {
if (runOneCompare(threadsNum)){
expectedCount++;
}
}
//计算有填充 有缓存对其的测试场景下响应时间更短的概率
System.out.println("Radio (Plain < Align ):" + expectedCount * 100D / testNum +"%");
}
public static void main(String[] args) throws Exception{
runOneSuit(NUM_THREADS, NUM_TEST_TIMES);
}
}
测试结果:
无填充 无缓存行对齐Plain:17638579323
有填充 有缓存行对齐Plain:7270967980
无填充 无缓存行对齐Plain:20392022924
有填充 有缓存行对齐Plain:7521045611
无填充 无缓存行对齐Plain:12537116135
有填充 有缓存行对齐Plain:7369732614
无填充 无缓存行对齐Plain:12498607757
有填充 有缓存行对齐Plain:7419091587
无填充 无缓存行对齐Plain:12198918237
有填充 有缓存行对齐Plain:7422019467
无填充 无缓存行对齐Plain:11988160497
有填充 有缓存行对齐Plain:7532538573
无填充 无缓存行对齐Plain:12223106752
有填充 有缓存行对齐Plain:7340185594
无填充 无缓存行对齐Plain:16388926182
有填充 有缓存行对齐Plain:7300794683
无填充 无缓存行对齐Plain:12338497888
有填充 有缓存行对齐Plain:7804763605
无填充 无缓存行对齐Plain:12393832355
有填充 有缓存行对齐Plain:7360913752
Radio (Plain < Align ):100.0%
此条信息也是碰巧在这篇文章中看到的:https://blog.csdn.net/hanmindaxiongdi/article/details/81159314
Java8中已经提供了官方的解决方案,Java8中新增了一个注解:@sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended才会生效。
对应到代码里在AlignHotVariable 类上加上注解,并且去掉填充6个对象,运行时在jvm里开启响应参数即可:
// 有填充 有缓存行对其的对象类
@sun.misc.Contended
static class AlignHotVariable extends PlainHotVariable{
// 用于填充,6个long 类型的变量 ,总占用内存为 6*8 = 48 字节
// 加上继承父类的一个变量value,那么总共该对象 占用内存为:8字节对象头 + 8字节父类变量+ 6*8字节填充变量 = 64字节,
// 正好满足一个对象全部在一个缓存行中,消除了伪竞争问题
// public long p1,p2,p3,p4,p5,p6;
}
以下是测试结果和 有填充项的基本一致:
无填充 无缓存行对齐Plain:19445985843
有填充 有缓存行对齐Plain:6996708098
无填充 无缓存行对齐Plain:12654238078
有填充 有缓存行对齐Plain:8071548517
无填充 无缓存行对齐Plain:19983041578
有填充 有缓存行对齐Plain:7074076269
无填充 无缓存行对齐Plain:17710330823
有填充 有缓存行对齐Plain:7030274857
无填充 无缓存行对齐Plain:20281301886
有填充 有缓存行对齐Plain:7048077452
无填充 无缓存行对齐Plain:19447443573
有填充 有缓存行对齐Plain:7066423588
无填充 无缓存行对齐Plain:20154352370
有填充 有缓存行对齐Plain:7052431719
无填充 无缓存行对齐Plain:18240658823
有填充 有缓存行对齐Plain:6996498595
无填充 无缓存行对齐Plain:19922299237
有填充 有缓存行对齐Plain:7094801775
无填充 无缓存行对齐Plain:17513743086
有填充 有缓存行对齐Plain:7002876176
Radio (Plain < Align ):100.0%