目录
1.1 JOL(java object layout)添加:
1. 布局工具使用
1.1 JOL(java object layout)添加:
此jar包是为了打印对象内存布局使用。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>put-the-version-here</version>
</dependency>
1.2 JOL使用
运行如下代码:
public class JavaObjectLayout {
public static void main(String[] args) {
/*打印虚拟机信息*/
System.out.println(VM.current().details());
}
}
details方法的源码中信息如下:
out.printf("# %-19s: %d, %d, %d, %d, %d, %d, %d, %d, %d [bytes]%n", "Field sizes by type", this.oopSize, this.sizes.booleanSize, this.sizes.byteSize, this.sizes.charSize, this.sizes.shortSize, this.sizes.intSize, this.sizes.floatSize, this.sizes.longSize, this.sizes.doubleSize);
out.printf("# %-19s: %d, %d, %d, %d, %d, %d, %d, %d, %d [bytes]%n", "Array element sizes", this.U.arrayIndexScale(Object[].class), this.U.arrayIndexScale(boolean[].class), this.U.arrayIndexScale(byte[].class), this.U.arrayIndexScale(char[].class), this.U.arrayIndexScale(short[].class), this.U.arrayIndexScale(int[].class), this.U.arrayIndexScale(float[].class), this.U.arrayIndexScale(long[].class), this.U.arrayIndexScale(double[].class));
运行结果为:打印的为此虚拟机基本数据类型与引用类型所占字节大小
"C:\Program Files\Java\jdk1.8.0_101\bin\java.exe"
# 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]
Field sizes by type(Field占用字节数):引用句柄, boolean,byte, char, short, int, float, long, double的长度
Array element sizes(数组占用字节数): 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]-数组类型的大小
1.3 打印内存局部实例
运行如下代码:
Object object=new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
Object[] objects=new Object[10];
System.out.println(ClassLayout.parseInstance(objects).toPrintable());
上述代码创建一个Object对象和长度为10的数组对象,并且使用ClassLayout打印出此对象的内存布局,运行结果如下:
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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[Ljava.lang.Object; 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) f2 22 00 f8 (11110010 00100010 00000000 11111000) (-134208782)
12 4 (object header) 0a 00 00 00 (00001010 00000000 00000000 00000000) (10)
16 40 java.lang.Object Object;.<elements> N/A
Instance size: 56 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
从结果中可以看出:
Instance size: 16 bytes 代表此object对象占用的内存大小为16byte,那16byte是什么呢?
Instance size: 56 bytes 代表此数组对象占用的内存为56bytes.
2. 对象内存布局
2.1 对象内存布局详解
下图是java普通对象与数组对象的内存布局图:
对象在内存中存储的布局分为三块区域:对象头,实例数据和对其填充。
1. markword
markword 固定长度8byte,描述对象的identityhashcode,分代年龄,锁信息等
2. classpoint
classpoint固定长度4byte, 指定该对象的class类对象。(JVM默认使用-XX:+UseCompressedClassPointers 参数进行压缩。如不使用可设置-XX:-UseCompressedClassPointers关闭,则该字段在64位jvm下占用8个字节; 可使用java -XX:+PrintCommandLineFlags -version 命令查看默认的或已设置的jvm参数)
3 . 实例数据:
基本变量:用于存放java八种基本类型成员变量,以4byte步长进行补齐,使用内存重排序优化空间
引用变量:存放对象地址,如String,Object;占用4个字节,64位jvm上默认使用-XX:+UseCompressedOops进行压缩, 可使用-XX:-UseCompressedOops进行关闭,则在64位jvm上会占用8个字节;
4. padding 补齐:
对象大小必须是8byte的整数倍,用来补齐字节数。Object o = new Object() 在内存中占用16个字节,其中最后4个是补 齐;至于为什么要补齐,在以后的学习中会进行讲解
5. 数组长度:
数组长度:如果是数组,额外占用固定4byte存放数组长度;
在1.3章节中的实例,运行结果说明:
Instance size: 16 bytes:markword(8 byte)+classpoint(4 byte)+padding(4 byte)
Instance size: 56 bytes: markword(8 byte)+classpoint(4 byte)+arrayLength(4 byte)+引用句柄(4*10)
2.1 对象头信息
1. 对象头分为两部分,一部分存储对象自身运行时数据,例如HashCode,GC分代年龄,锁状态等。
2. 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针确定对象所属实例。但是并不是所有的对象数据保留类型指针,换句话说查找对象的元数据信息并不一定经过对象本身
3. 如果对象是一个java数组,对象头中必须有一块用于记录数组长度的数据。因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数据的元数据中无法确定数组的大小
下图是64位虚拟机对象头,markword的信息:这其中包含锁升级的过程,在以后讲锁升级时会进行讲解。
3.对象访问定位
说明:java程序通过栈上的reference数据来操作堆上的具体对象,目前主流的访问对象方式又两种,使用句柄和直接指针两种
3.1 句柄访问
用句柄的方式,在java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。好处时reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。
3.2 直接指针访问
使用直接指针方式访问,java堆对象的布局中就必须考虑如何防止访问类型数据的相关信息,而reference中存储的直接就是对象地址。好处是速度快,节省了一次指针定位的时间开销,hotspot基于此实现
参考资料:
1. 《深入理解Java虚拟机》2.3章节