在主存中缓存是以cache line为单元存储的。cache line的长度是2的指数倍,一般为32到256之间。大部分cache line的长度为64 bytes。伪共享是指当不同线程修改在同一个cache line中的多个不相关变量时,造成性能下降的问题。在SMP系统中,在cache line中同时写入也是多线程并发执行的一个重要限制因素。
为保证多个线程可线型化执行,必须确保任何两个线程不会同时写同一个变量或同一个cache line。对同一个变量的操作可以在代码级别上进行追踪。对于cache line上的多变量写,我们需要知道内存布局或者通过工具来检测。比如,Intel VTune。这篇文章将解释Java对象在内存中是如何分布的,以及我们如何避免伪共享。
上图所示为伪共享的示例。一个线程在Core1上运行,更新变量X,另一个线程在Core2上更新变量Y。但两个变量在同一个cache line上。每个线程都会竞争cache line的所有权,用于更新。一旦Core1获取权限,Core2对cache line将不起效,反之亦然。这将造成所有权的来回转换影响L3 cache的性能。当这些处理的Core被不同的socket占用,并且需要通过socket链接时,情况会更加恶化。
Hotspot JVM中,所有的对象都有两个字(非4bytes)的头。第一个是"mark"字,由24bits的hashcode和8bits的flags(用于锁的状态或被替换为锁对象),共32bits; 第二个字是对象类的引用。Arrays有一个额外的字用于数组的长度。每个对象被分配了8bytes大小的边界用于性能。对象的fields被重排序,把声明顺序变为依据byte大小的顺序。
- doubles (8) and longs (8)
- ints (4) and floats (4)
- shorts (2) and chars (2)
- booleans (1) and bytes (1)
- references (4/8)
- <repeat for sub-class fields>
代码如下
public final class FalseSharing
implements Runnable
{
public final static int NUM_THREADS = 4; // change
public final static long ITERATIONS = 500L * 1000L * 1000L;
private final int arrayIndex;
private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
static
{
for (int i = 0; i < longs.length; i++)
{
longs[i] = new VolatileLong();
}
}
public FalseSharing(final int arrayIndex)
{
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception
{
final long start = System.nanoTime();
runTest();
System.out.println("duration = " + (System.nanoTime() - start));
}
private static void runTest() throws InterruptedException
{
Thread[] threads = new Thread[NUM_THREADS];
for (int i = 0; i < threads.length; i++)
{
threads[i] = new Thread(new FalseSharing(i));
}
for (Thread t : threads)
{
t.start();
}
for (Thread t : threads)
{
t.join();
}
}
public void run()
{
long i = ITERATIONS + 1;
while (0 != --i)
{
longs[arrayIndex].value = i;
}
}
public final static class VolatileLong
{
public volatile long value = 0L;
public long p1, p2, p3, p4, p5, p6; // comment out
}
}
结果如下:
运行上述代码,通过增加和删除cache line,改变线程个数,得到如下图的结果。
伪共享的影响一目了然。在没有cache line竞争的情况下,可以达到线性。