本文将通过JOL分析Java对象的内存布局,包括伪共享DataModelExternals数组对齐等内容
代码托管在https://github.com/zhongmingmao/java_object_layout

伪共享

Java8引入@sun.misc.Contended注解,自动进行缓存行填充原始支持解决伪共享问题,实现高效并发,关于伪共享,网上已经很多资料,请参考下列连接:

  1. https://yq.aliyun.com/articles/62865
  2. http://www.cnblogs.com/Binhua-Liu/p/5620339.html
  3. http://blog.csdn.net/zero__007/article/details/54951584
  4. http://blog.csdn.net/aigoogle/article/details/41517213
  5. http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/share/classes/sun/misc/Contended.java

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
// JVM Args : -Djol.tryWithSudo=true  -XX:-RestrictContended
public class JOLSample_09_Contended {

public static void main(String[] args) throws Exception {
out.println(VM.current().details());
out.println(ClassLayout.parseClass(A.class).toPrintable());
}

@sun.misc.Contended
public static class A {
int a;
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

me.zhongmingmao.jol.JOLSample_09_Contended$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 128 (alignment/padding gap)
140 4 int A.a N/A
Instance size: 144 bytes
Space losses: 128 bytes internal + 0 bytes external = 128 bytes total

分析

  1. 本机CPU采用的Intel I5 BroadwellCache Line Size64 Bytes,相关链接Broadwell
  2. 在对象头部后插入了128 BytesPadding,这样保证了同一缓存行中,不可能同时容纳2JOLSample_09_Contended$A实例,避免了伪共享的问题
  3. @Contended注解的相关概念请查看代码Contended.java

性能对比代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// JVM Args : -Djol.tryWithSudo=true  -XX:-RestrictContended
public class FalseSharingTest {

public final static int THREAD_COUNT = 4;

@Ignore("take too long")
@Test
public void falseSharingTest() throws InterruptedException {
long commonDuration = duration(CommonLong.class); // 29.189s
long contendedDuration = duration(ContendedLong.class); // 11.047s
assertTrue(commonDuration / (contendedDuration + 0.0) > 1); // 29.189/11.047 ≈ 2.64,性能提升很明显
}

private long duration(Class<? extends VolatileLong> clazz) throws InterruptedException {
out.println(ClassLayout.parseClass(clazz).toPrintable());
VolatileLong[] longs = new VolatileLong[THREAD_COUNT];
IntStream.range(0, longs.length).forEach(value -> {
try {
longs[value] = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
});

ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
IntStream.range(0, THREAD_COUNT).forEach(value -> pool.submit(new Task(value, longs)));
pool.shutdown();
LocalDateTime start = LocalDateTime.now();
while (!pool.awaitTermination(100, TimeUnit.MILLISECONDS)) {
}
Duration duration = Duration.between(start, LocalDateTime.now());
out.println(String.format("%s , duration : %s\n", clazz.getSimpleName(), duration));
return duration.toNanos();
}


@AllArgsConstructor
private class Task implements Runnable {
public static final long ITERATIONS = 500L * 1000L * 1000L;
private final int index;
private final VolatileLong[] longs;

@Override
public void run() {
LongStream.range(0, ITERATIONS).forEach(longs[index]::setValue);
}
}

private interface VolatileLong {
void setValue(long value);
}

@Data
private static class CommonLong implements VolatileLong {
public volatile long value = 0L; // 暴露因缓存行而导致的伪共享问题
}

@Data
@sun.misc.Contended
private static class ContendedLong implements VolatileLong {
public volatile long value = 0L;
}
}

性能对比结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
me.zhongmingmao.jol.FalseSharingTest$CommonLong object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (alignment/padding gap)
16 8 long CommonLong.value N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

CommonLong , duration : PT29.189S

me.zhongmingmao.jol.FalseSharingTest$ContendedLong object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 132 (alignment/padding gap)
144 8 long ContendedLong.value N/A
Instance size: 152 bytes
Space losses: 132 bytes internal + 0 bytes external = 132 bytes total

ContendedLong , duration : PT11.047S

DataModel

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// JVM Args : -Djol.tryWithSudo=true
public class JOLSample_10_DataModels {

public static void main(String[] args) throws Exception {
Layouter layouter;

layouter = new CurrentLayouter();
System.out.println("***** " + layouter);
System.out.println(ClassLayout.parseClass(A.class, layouter).toPrintable());

layouter = new HotSpotLayouter(new X86_32_DataModel());
System.out.println("***** " + layouter);
System.out.println(ClassLayout.parseClass(A.class, layouter).toPrintable());

layouter = new HotSpotLayouter(new X86_64_DataModel());
System.out.println("***** " + layouter);
System.out.println(ClassLayout.parseClass(A.class, layouter).toPrintable());

layouter = new HotSpotLayouter(new X86_64_COOPS_DataModel());
System.out.println("***** " + layouter);
System.out.println(ClassLayout.parseClass(A.class, layouter).toPrintable());
}

public static class A {
Object a;
Object b;
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
***** Current VM Layout
me.zhongmingmao.jol.JOLSample_10_DataModels$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 java.lang.Object A.a N/A
16 4 java.lang.Object A.b N/A
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

***** VM Layout Simulation (X32 model, 8-byte aligned, compact fields, field allocation style: 1)
me.zhongmingmao.jol.JOLSample_10_DataModels$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 8 (object header) N/A
8 4 java.lang.Object A.a N/A
12 4 java.lang.Object A.b N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

***** VM Layout Simulation (X64 model, 8-byte aligned, compact fields, field allocation style: 1)
me.zhongmingmao.jol.JOLSample_10_DataModels$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 8 java.lang.Object A.a N/A
24 8 java.lang.Object A.b N/A
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

***** VM Layout Simulation (X64 model (compressed oops), 8-byte aligned, compact fields, field allocation style: 1)
me.zhongmingmao.jol.JOLSample_10_DataModels$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 java.lang.Object A.a N/A
16 4 java.lang.Object A.b N/A
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

分析

  1. CurrentLayouter真实的内存布局,HotSpotLayouter模拟的内存布局
  2. 通过parseInstance(Object instance, Layouter layouter)能够观察在不同DataModelsynchronized锁升级的过程

Externals

之前显示的都是internals size(即shallow size),这里展示externals sizeretained size=internals size+externals size

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// JVM Args : -Djol.tryWithSudo=true
public class JOLSample_16_AL_LL {

public static void main(String[] args) throws Exception {
out.println(VM.current().details());
PrintWriter pw = new PrintWriter(out);

int objectCount = 4;
Object[] objects = new Object[objectCount];
for (int i = 0; i < objectCount; i++) {
if (new Random().nextInt() % 2 == 0) {
objects[i] = new A(i);
} else {
objects[i] = new B(i);
}
}

pw.println(GraphLayout.parseInstance(objects).toFootprint());
pw.close();
}

@AllArgsConstructor
public static class A {
int a;
}

@AllArgsConstructor
public static class B {
long b;
}
}

运行结果

1
2
3
4
5
me.zhongmingmao.jol.JOLSample_16_AL_LL$A@7de26db8d, me.zhongmingmao.jol.JOLSample_16_AL_LL$B@1175e2dbd, me.zhongmingmao.jol.JOLSample_16_AL_LL$B@36aa7bc2d, me.zhongmingmao.jol.JOLSample_16_AL_LL$B@76ccd017d footprint:
COUNT AVG SUM DESCRIPTION
1 16 16 me.zhongmingmao.jol.JOLSample_16_AL_LL$A
3 24 72 me.zhongmingmao.jol.JOLSample_16_AL_LL$B
4 88 (total)

分析

遍历数组objects相连的外部对象,并做了统计

数组对齐

代码

1
2
3
4
5
6
7
8
9
10
11
// JVM Args : -Djol.tryWithSudo=true
public class JOLSample_26_ArrayAlignment {

public static void main(String[] args) throws Exception {
out.println(VM.current().details());
out.println(ClassLayout.parseInstance(new short[0]).toPrintable());
for (int size = 1; size <= 5; size += 2) {
out.println(ClassLayout.parseInstance(new short[size]).toPrintable());
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

[S object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 31 01 00 f8 (00110001 00000001 00000000 11111000) (-134217423)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 0 short [S.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

[S object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 31 01 00 f8 (00110001 00000001 00000000 11111000) (-134217423)
12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
16 2 short [S.<elements> N/A
18 6 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total

[S object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 31 01 00 f8 (00110001 00000001 00000000 11111000) (-134217423)
12 4 (object header) 03 00 00 00 (00000011 00000000 00000000 00000000) (3)
16 6 short [S.<elements> N/A
22 2 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

[S object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 31 01 00 f8 (00110001 00000001 00000000 11111000) (-134217423)
12 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
16 10 short [S.<elements> N/A
26 6 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total

对象布局

分析

数组对齐与前面字节对齐的例子类似,在64-bit Hotspot JVM8 Bytes对齐