共计 3840 个字符,预计需要花费 10 分钟才能阅读完成。
简介
在之前的文章中,咱们介绍了应用 JOL 这一神器来解析 java 类或者 java 实例在内存中占用的空间地址。
明天,咱们会更进一步,分析一下在之前文章中没有解说到的更深层次的细节。一起来看看吧。
对象和其暗藏的机密
java.lang.Object 大家应该都很相熟了,Object 是 java 中所有对象的鼻祖。
接下来咱们来对这个 java 对象的鼻祖进行一个具体的解剖剖析,从而了解 JVM 的深层次的机密。
工具当然是应用 JOL:
@Slf4j
public class JolUsage {
@Test
public void useJol(){log.info("{}", VM.current().details());
log.info("{}", ClassLayout.parseClass(Object.class).toPrintable());
log.info("{}", ClassLayout.parseInstance(new Object()).toPrintable());
}
}
代码很简略,咱们打印 JVM 的信息,Object class 和一个新的 Object 实例的信息。
看下输入:
[main] INFO com.flydean.JolUsage - # Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# 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]
10:27:32.311 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
10:27:32.312 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 86 06 00 00 (10000110 00000110 00000000 00000000) (1670)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
从下面的后果咱们晓得,在 64 位的 JVM 中,一个 Object 实例是占用 16 个字节。
因为 Object 对象中并没有其余对象的援用,所以咱们看到 Object 对象只有一个 12 字节的对象头。剩下的 4 个字节是填充位。
Object 对象头
那么这 12 字节的对象头是做什么用的呢?
如果想要深刻理解这 12 字节的对象头,当然是要去研读一下 JVM 的源码:src/share/vm/oops/markOop.hpp。
有趣味的小伙伴能够去看看。如果没有趣味,没关系,这里给大家一个张总结的图:
javaObject 对象的对象头大小依据你应用的是 32 位还是 64 位的虚拟机的不同,稍有变动。这里咱们应用的是 64 位的虚拟机为例。
Object 的对象头,分为两局部,第一局部是 Mark Word,用来存储对象的运行时数据比方:hashcode,GC 分代年龄,锁状态,持有锁信息,偏差锁的 thread ID 等等。
在 64 位的虚拟机中,Mark Word 是 64bits,如果是在 32 位的虚拟机中 Mark Word 是 32bits。
第二局部就是 Klass Word,Klass Word 是一个类型指针,指向 class 的元数据,JVM 通过 Klass Word 来判断该对象是哪个 class 的实例。
且慢!
有的小伙伴可能发现了问题,之前咱们用 JOL 解析 Object 对象的时候,Object head 大小是 12 字节,也就是 96bits,这里怎么写的是 128bits?
没错,如果没有开启 COOPs 就是 128bits,如果开启了 COOPs,那么 Klass Word 的大小就从 64bits 降到了 32bits。
还记得咱们之前讲的 COOPs 吗?
COOPs 就是压缩对象指针技术。
对象指针用来指向一个对象,示意对该对象的援用。通常来说在 64 位机子下面,一个指针占用 64 位,也就是 8 个字节。而在 32 位机子下面,一个指针占用 32 位,也就是 4 个字节。
实时上,在应用程序中,这种对象的指针是十分十分多的,从而导致如果同样一个程序,在 32 位机子下面运行和在 64 位机子下面运行占用的内存是齐全不同的。64 位机子内存应用可能是 32 位机子的 1.5 倍。
而压缩对象指针,就是指把 64 位的指针压缩到 32 位。
怎么压缩呢?64 位机子的对象地址依然是 64 位的。压缩过的 32 位存的只是绝对于 heap base address 的位移。
咱们应用 64 位的 heap base 地址 + 32 位的地址位移量,就失去了理论的 64 位 heap 地址。
对象指针压缩在 Java SE 6u23 默认开启。在此之前,能够应用 -XX:+UseCompressedOops 来开启。
数组对象头
java 中有一个十分特地的对象叫做数组,数组的对象头和 Object 有什么区别吗?
咱们用 JOL 再看一次:
log.info("{}",ClassLayout.parseClass(byte[].class).toPrintable());
log.info("{}",ClassLayout.parseInstance("www.flydean.com".getBytes()).toPrintable());
下面的例子中咱们别离解析了 byte 数组的 class 和 byte 数组的实例:
10:27:32.396 [main] INFO com.flydean.JolUsage - [B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 0 byte [B.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
10:27:32.404 [main] INFO com.flydean.JolUsage - [B 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) 22 13 07 00 (00100010 00010011 00000111 00000000) (463650)
12 4 (object header) 0f 00 00 00 (00001111 00000000 00000000 00000000) (15)
16 15 byte [B.<elements> N/A
31 1 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 1 bytes external = 1 bytes total
看到区别了吗?咱们发现数组的对象头是 16 字节,比一般对象的对象头多出了 4 个字节。这 4 个字节就是数组的长度。
整个对象的构造
好了,写到这里咱们来总结一下,java 对象的构造能够分为一般 java 对象和数组对象两种:
数组对象在对象头中多了一个 4 字节的长度字段。
大家看到最初的字节是 padding 填充字节,为什么要填充呢?
因为 JVM 是以 8 字节为单位进行对其的,如果不是 8 字节的整数倍,则须要补全。
本文链接:http://www.flydean.com/jvm-java-object-in-heap/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!