java对象内存布局简介
java对象的内存布局包含对象头、实例数据、对齐填充
对象头
- markword:对象默认的hash码、分代年龄,锁的状态标识等。
- class point:指向对象对应的类的元数据内存地址(堆指向方法区)。也就是指向Class的字节码。
- length :数组对象持有的数组长度,只有数组特有。
markword
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。
markword多长呢,是分虚拟机的。32位jvm的mark的长度是32,,4位jvm的markword的长度是64。具体存储的内容及内容长度如下:
32位jvm的markword存储: 64位jvm的markword存储: 上面的两个图片如何解析呢? 无锁状态下64位jvm前25bit没用到,存0; 接下来的31bit存对象的hashCode; 接下来的1bit未用到,存0; 接下来的4bit存分代年龄; 接下来的1bit存是否偏向锁; 接下来的2bit存锁标识位; 这样就知道64位的markword都存了什么东西。其他的锁状态分析与无锁状态类似。
指针
上面的markword存储的是运行时数据,比如这个对象的hashcode、锁的状态、垃圾回收信息等。 而指针中存储的的是指针,指针是干嘛的?指针是指向方法区的,说明这个对象是什么类型的。 64位jvm指针的长度是64bit。 需要注意的是,jdk1.8默认是开启压缩的,压缩完的指针就变成了32bit。可以通过参数-XX:-UseCompressedOops关闭指针压缩。
length
length中存储的是数组长度,64位jvm数组长度占32bit。
实例数据
主要是存放类的数据信息,父类的信息,对象字段属性信息。
如果对象有属性字段,则这里会有数据信息。如果对象无属性字段,则这里就不会有数据。根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等;
对齐填充
为了字节对齐,填充的数据。不是必须的,因为对齐了就不用填充了。
对象可以有对齐数据也可以没有。默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的倍数。如果一个对象用不到8N个字节则需要对其填充,以此来补齐对象头和实例数据占用内存之后剩余的空间大小。如果对象头和实例数据已经占满了JVM所分配的内存空间,那么就不用再进行对齐填充了。
所有的对象分配的字节总SIZE需要是8的倍数,如果前面的对象头和实例数据占用的总SIZE不满足要求,则通过对齐数据来填满。
为什么要对齐数据?字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址。
汇总图
java对象的内存布局实例演示
下面写的测试代码主要打印两个对象的内存布局。一个是普通对象student,一个是数据对象strings。
代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-demo</artifactId>
<groupId>org.ludengke</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>study</artifactId>
<dependencies>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
</dependencies>
</project>
package binary;
import org.openjdk.jol.info.ClassLayout;
public class TestObjectStruct {
static Student student = new Student();
static String[] strings={"1","sdfsdf","232"};
public static void main(String[] args) {
System.out.println("student的结构:");
System.out.println(student.hashCode());
System.out.println(Integer.toHexString(student.hashCode()));
System.out.println(ClassLayout.parseInstance(student).toPrintable());
System.out.println("strings的结构:");
System.out.println(strings.hashCode());
System.out.println(Integer.toHexString(strings.hashCode()));
System.out.println(ClassLayout.parseInstance(strings).toPrintable());
}
}
-
执行结果 -
结果解析
这张图是64位虚拟机,未进行指针压缩的执行结果。
图中输出了两个对象,一个是普通对象的student对象,一个是数组对象strings对象。数组对象中多了数组长度。
student对象的说明信息比较多,详细说明了对象头、实例数据、填充数据。可以由于图片篇幅有限,将前8位的说明画到string对象中。
为什么图片中的说明与上面《64位jvm的markword存储》图片的顺序不一致呢,正好是倒序? 这是涉及到一个知识点大端存储与小端存储,这里不详细叙述。我们这里之所以倒序,是因为大端存储的原因。
其它锁状态的验证有空再研究。
参考博客: https://blog.csdn.net/baidu_28523317/article/details/104453927 https://www.cnblogs.com/jajian/p/13681781.html
|