0. details
@Test
void testDetails() {
String details = VM.current().details();
System.out.println(details);
}
结果说明:
VM mode: 64 bits 64位虚拟机
Compressed references (oops): 3-bit shift 压缩了的普通对象指针
Compressed class pointers: 0-bit shift and 0x26510000000 base 压缩类指针
Object alignment: 8 bytes 对象对齐
属性名 | ref | bool | byte | char | shrt | int | flt | lng | dbl |
---|---|---|---|---|---|---|---|---|---|
Field sizes | 4 | 1 | 1 | 2 | 2 | 4 | 4 | 8 | 8 |
Array element sizes | 4 | 1 | 1 | 2 | 2 | 4 | 4 | 8 | 8 |
Array base offsets | 16 | 16 | 16 | 16 | 16 | 16 | 16 | 16 | 16 |
Field sizes(字段大小
): 4, 1, 1, 2, 2, 4, 4, 8, 8
Array element sizes(数组元素大小
): 4, 1, 1, 2, 2, 4, 4, 8, 8
Array base offsets(数组基偏移量
): 16, 16, 16, 16, 16, 16, 16, 16, 16
-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针
-XX:+UseCompressedOops //开启压缩普通对象指针
-XX:-UseCompressedOops //关闭压缩普通对象指针
1. basic
public class JolDemo02Test {
public static class A {
boolean f;
}
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
}
}
不开启压缩类指针(-XX:-UseCompressedClassPointers),结果如下:
org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 8 (object header: class) N/A
16 1 boolean A.f N/A
17 7 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
开启压缩类指针(-XX:+UseCompressedClassPointers),结果如下:
org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 1 boolean A.f N/A
13 3 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
如不说明,下面测试结果都是开启压缩
2. 对齐
In this example, we can see the long field
* is indeed aligned for 8 bytes, sometimes making the gap after the
* object header.
*/
这是个更高级的属性布局样例。由于底层硬件平台总是要求对齐,为了获得更好的性能和正确性。期待属性根据自身占据空间的大小对齐,对于boolean类型来说,这并没有什么,但是对于long类型来说,存在不同的地方。这下面的样例中我们发现long类型的属性缺失占了8个字节,有时和对象头之间存在一个gap
public class JolDemo02Test {
public static class A {
boolean f;
long f2;
}
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
}
}
结果如下(和boolean属性之间存在gap):
org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 1 boolean A.f N/A
13 3 (alignment/padding gap)
16 8 long A.f2 N/A
Instance size: 24 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
修改代码:
public class JolDemo02Test {
public static class A {
long f2;
//boolean f;
}
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
}
}
结果如下(和对象头之间存在gap):
org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 (alignment/padding gap)
16 8 long A.f2 N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
3. packing
这是VM如何打包字段的一个示例。
JVM 通过紧密排列字段来最小化内存占用。运行此示例,可以看到字段被密集地打包,且空隙最小。这是通过按 8->4->2->1 的顺序对齐字段来实现的,因为一旦对齐了 8 字节的字段,就不能打破初始对齐。初始 8 字节对齐产生的空隙可以被一个或多个较小的字段占用。
注意,实际的字段顺序与声明的顺序非常不同。JVM 规范没有要求必须按照声明的顺序排列。
代码如下:
public class JolDemo03Test {
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
}
public static class A {
boolean bo1, bo2;
byte b1, b2;
char c1, c2;
double d1, d2;
float f1, f2;
int i1, i2;
long l1, l2;
short s1, s2;
}
}
结果如下:
org.example.jucdemo2.jol.JolDemo03Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 float A.f1 N/A
16 8 double A.d1 N/A
24 8 double A.d2 N/A
32 8 long A.l1 N/A
40 8 long A.l2 N/A
48 4 float A.f2 N/A
52 4 int A.i1 N/A
56 4 int A.i2 N/A
60 2 char A.c1 N/A
62 2 char A.c2 N/A
64 2 short A.s1 N/A
66 2 short A.s2 N/A
68 1 boolean A.bo1 N/A
69 1 boolean A.bo2 N/A
70 1 byte A.b1 N/A
71 1 byte A.b2 N/A
Instance size: 72 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
4 继承
该示例,展示了VM如何在类层次结构中布局字段。
VM 需要维护的一个重要不变量是,无论通过哪个类访问字段,这些可访问字段都位于相同的偏移量。
也就是说,对于下面的类 B 和 C,字段 A.a 应该位于相同的偏移量。这促使 VM 首先布局超类的字段。
代码如下:
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JolDemo04Test {
public static void main(String[] args) {
out.println(ClassLayout.parseClass(A.class).toPrintable());
out.println();
out.println(ClassLayout.parseClass(B.class).toPrintable());
out.println();
out.println(ClassLayout.parseClass(C.class).toPrintable());
}
public static class A {
int a;
private int d; // 子类会保存这个信息
static int r;
public void test(){}
}
public static class B extends A {
int b;
protected int e;
}
public static class C extends B {
int c;
}
}
结果如下:
- 静态属性不在对象数据里面,方法也不在对象数据里面。
- 子类的对象数据里面保存了父类的私有变量数据信息
- VM 首先布局父类的字段。
org.example.jucdemo2.jol.JolDemo04Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int A.a N/A
16 4 int A.d N/A
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
org.example.jucdemo2.jol.JolDemo04Test$B object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int A.a N/A
16 4 int A.d N/A
20 4 int B.b N/A
24 4 int B.e N/A
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
org.example.jucdemo2.jol.JolDemo04Test$C object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int A.a N/A
16 4 int A.d N/A
20 4 int B.b N/A
24 4 int B.e N/A
28 4 int C.c N/A
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
5.
6.
7. 异常
这个示例展示了VM如何特殊处理某些字段。
在JDK 8及更低版本中,你会在Throwable类中看到可疑的空隙。如果你查看Java源代码,会发现Throwable.backtrace字段,这个字段在内存转储中并未列出。这是因为该字段处理的是VM内部信息,任何时候都不应让用户访问。
在JDK 9及更高版本中,该字段再次可见,并且保证包含Java可读的内容。
参见:
https://bugs.openjdk.java.net/browse/JDK-4496456
https://bugs.openjdk.java.net/browse/JDK-8033735
代码如下:
public class JolDemo07Test {
public static void main(String[] args) {
out.println(ClassLayout.parseClass(Throwable.class).toPrintable());
}
}
结果如下:
java.lang.Throwable object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int Throwable.depth N/A
16 4 java.lang.Object Throwable.backtrace N/A
20 4 java.lang.String Throwable.detailMessage N/A
24 4 java.lang.Throwable Throwable.cause N/A
28 4 java.lang.StackTraceElement[] Throwable.stackTrace N/A
32 4 java.util.List Throwable.suppressedExceptions N/A
36 4 (object alignment gap)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
8. Class
该示例展示了某些字段的特殊处理。
如果你运行这个示例,可以看到实例字段块中的大空隙。没有Java字段可以占据这个块,因此这里没有“隐藏”字段,如前面的示例所示。这次,VM将一些字段“注入”到Class中,用于存储一些元信息。
public class JolDemo08Test {
public static void main(String[] args) {
out.println(ClassLayout.parseClass(Class.class).toPrintable());
}
}
结果如下:
java.lang.Class object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int Class.classRedefinedCount N/A
16 24 (alignment/padding gap)
40 4 java.lang.reflect.Constructor Class.cachedConstructor N/A
44 4 java.lang.String Class.name N/A
48 4 java.lang.Module Class.module N/A
52 8 (alignment/padding gap)
60 4 java.lang.String Class.packageName N/A
64 4 java.lang.Class Class.componentType N/A
68 4 java.lang.ref.SoftReference Class.reflectionData N/A
72 4 sun.reflect.generics.repository.ClassRepository Class.genericInfo N/A
76 4 java.lang.Object[] Class.enumConstants N/A
80 4 java.util.Map Class.enumConstantDirectory N/A
84 4 java.lang.Class.AnnotationData Class.annotationData N/A
88 4 sun.reflect.annotation.AnnotationType Class.annotationType N/A
92 4 java.lang.ClassValue.ClassValueMap Class.classValueMap N/A
Instance size: 96 bytes
Space losses: 32 bytes internal + 0 bytes external = 32 bytes total
11 Classword
代码如下:
public class JolDemo11Test {
public static void main(String[] args) {
out.println(ClassLayout.parseInstance(new A()).toPrintable());
out.println(ClassLayout.parseInstance(new B()).toPrintable());
}
public static class A {
}
public static class B {
}
}
结果如下:
对象头(object header: class)对应的值不一样
org.example.jucdemo2.jol.JolDemo11Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
org.example.jucdemo2.jol.JolDemo11Test$B object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x0101da20
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
12 偏向锁
这是对标记字(mark word)的一次深入探讨。
标记字除了存储其他信息外,还存储锁定信息。我们可以清楚地看到,在我们获取锁并随后释放锁时,标记字的内容是如何变化的。
在这个示例中,我们演示了偏向锁定。每个Java对象都有可能是同步的目标。大多数情况下,对象只被单个线程锁定。在这种情况下,我们可以将对象“偏向”到那个单个线程,从而使对该对象的同步非常廉价。
为了演示这一点,我们在获取锁之前、期间和之后打印对象的内部信息。你可以注意到,标记字从“可偏向”变为“已偏向”。解锁后,标记字保持不变:对象现在偏向于该线程。
在JDK 9之前,默认情况下,偏向锁定只有在VM启动5秒后才会启用。因此,最佳做法是在JDK 8及更低版本上使用-XX:BiasedLockingStartupDelay=0运行测试。
从JDK 15开始,默认情况下禁用了偏向锁定,因此需要使用-XX:+UseBiasedLocking来运行此测试。
代码如下:
public class JolDemo12Test {
public static void main(String[] args) {
final A a = new A();
ClassLayout layout = ClassLayout.parseInstance(a);
out.println("**** Fresh object");
out.println(layout.toPrintable());
synchronized (a) {
out.println("**** With the lock");
out.println(layout.toPrintable());
}
out.println("**** After the lock");
out.println(layout.toPrintable());
}
public static class A {
}
}
执行时增加虚拟机参数 -XX:+UseBiasedLocking
, 开启偏向锁,结果如下:
注:Java HotSpot(TM) 64-Bit Server VM warning: Option UseBiasedLocking was deprecated in version 15.0 and will likely be removed in a future release.
**** Fresh object
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** With the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000001d7a0d50805 (biased: 0x0000000075e83542; epoch: 0; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** After the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000001d7a0d50805 (biased: 0x0000000075e83542; epoch: 0; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
13 ThinLocking
执行时开启偏向锁 -XX:-UseBiasedLocking
, 结果如下:
对比发现:加锁之后是0x000000e581dff5d8 (thin lock: 0x000000e581dff5d8)
**** Fresh object
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** With the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000e581dff5d8 (thin lock: 0x000000e581dff5d8)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** After the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
14 FatLocking
这是一个胖锁(fat locking)的示例。
如果VM检测到线程之间的竞争,它需要将访问仲裁委托给操作系统。这涉及将对象与本地锁关联起来,即“膨胀”(inflate)锁。
在这个示例中,我们需要模拟竞争,因此引入了一个额外的线程。你可以看到,新对象具有默认的标记字,而对象在辅助线程获取锁之前已经有一个标记字,当主线程最终获取锁时,锁已经被“膨胀”了。即使锁被释放后,膨胀状态仍然保留。
在某些JDK版本中,锁在垃圾回收(GC)后会被“缩减”(deflate)。在JDK 15之前,锁清理在安全点(safepoint)进行,因此任何GC都会进入该代码。从JDK 15开始,监视器在不再使用的数量足够多时异步地被缩减。
代码如下:
public class JolDemo14Test {
public static void main(String[] args) throws Exception {
final A a = new A();
ClassLayout layout = ClassLayout.parseInstance(a);
out.println("**** Fresh object");
out.println(layout.toPrintable());
Thread t = new Thread(() -> {
synchronized (a) {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// Do nothing
}
}
});
t.start();
TimeUnit.SECONDS.sleep(1);
out.println("**** Before the lock");
out.println(layout.toPrintable());
synchronized (a) {
out.println("**** With the lock");
out.println(layout.toPrintable());
}
out.println("**** After the lock");
out.println(layout.toPrintable());
System.gc();
out.println("**** After System.gc()");
out.println(layout.toPrintable());
}
public static class A {}
}
结果如下:
**** Fresh object
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** Before the lock
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000032a41ff700 (thin lock: 0x00000032a41ff700)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** With the lock
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000017159410ce2 (fat lock: 0x0000017159410ce2)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** After the lock
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000017159410ce2 (fat lock: 0x0000017159410ce2)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
**** After System.gc()
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000017159410ce2 (fat lock: 0x0000017159410ce2)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15 hashcode
这是一个身份哈希码(identity hash code)的示例。
一旦计算出身份哈希码,它就应该保持不变。HotSpot 选择将哈希码存储在标记字中。
一旦计算出哈希码,你可以在对象头中清楚地看到哈希码的字节。
代码如下:
public class JolDemo15Test {
public static void main(String[] args) {
final A a = new A();
ClassLayout layout = ClassLayout.parseInstance(a);
out.println("**** Fresh object");
out.println(layout.toPrintable());
out.println("-------------->hashCode: " + Integer.toHexString(a.hashCode()));
out.println();
out.println("**** After identityHashCode()");
out.println(layout.toPrintable());
}
public static class A {}
}
**** Fresh object
org.example.jucdemo2.jol.JolDemo15Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-------------->hashCode: 2096442d
**** After identityHashCode()
org.example.jucdemo2.jol.JolDemo15Test$A object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000002096442d01 (hash: 0x2096442d; age: 0)
8 4 (object header: class) 0x01001200
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total