文章目录
- 0、前置
- 1、对象的内存布局
- 2、对象头之对象标记Mark Word
- 3、对象头之类元信息
- 4、实例数据
- 5、对齐填充
- 6、对象内存布局之JOL证明
- 7、对象分代年龄
- 8、压缩指针
0、前置
heap(堆区),分为新生区new、养老区old、元空间Metaspace,其中new区又分为伊甸园eden、幸存者0区s0、幸存者1区s1
new一个对象,在内存中的位置是堆 ⇒ 新生区⇒ 伊甸园区
Object object = new Object();
1、对象的内存布局
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据 (Instance Data)、对齐填充 (Padding),其中,对齐填充用于保证对象内存总长度是8个字节的倍数。
对象头:
对象头分为对象标记markOop和类元信息klassOop(两种英文都行,Oop用于JDK源代码)
2、对象头之对象标记Mark Word
public class ObjectHeadDemo {public static void main(String[] args) {Object o = new Object(); //这样new一个对象,占内存多少?System.out.println(o.hashCode()); //这个hashcode记录在对象的什么地方?synchronized (o){ //...同步锁标记?}System.gc(); //15次gc后,还没被处理的,可从新生区移动到养老区,怎么记录次数的?}
}
以下是对象头中的对象标记的结构:
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。单看Mark Word,8字节,64Bit,结构如下:
Mark Word中默认存储对象的HashCode、分代年龄和锁标志位等信息。这些信息都与对象自身的属性定义无关,所以MarkWord被设计成一个非固定的数据结构,以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化
总之,对象布局、GC回收和锁升级就是对象标记MarkWord里面标志位的变化。
3、对象头之类元信息
类型指针,指向对象的类元数据,虚拟机通过这个指针来确定这个对象是哪个类的实例
public class Customer{int id;String name;
}//三个对象,同一个类,即同一个模板,对象头的类元指针指向相同
Customer c1 = new Customer();
Customer c2 = new Customer();
Customer c3 = new Customer();
一句话,每次new对象出来的,指向那个统一的多个实例对象的那个模板。
Q:对象头多大?
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。
4、实例数据
存放类的属性(Field)数据信息,包括父类的属性信息
如下:此时new这个对象,没有属性,只有一个对象头,16字节大小(忽略压缩指针的影响)
class Customer{}
引入属性:一个int 4 字节,一个boolean 1字节,16+4+1=21字节
class Customer{int id;boolean flag;
}
此时,21字节不是8的整数倍,需要进行对齐填充,补到24字节。
5、对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。
6、对象内存布局之JOL证明
JOL即Java Object LayOut,一个开箱即用的小工具,用于分析和展示Java对象在JVM中的大小布局。JOL官网:http://openjdk.java.net/projects/code-tools/jol/ 。
<!--jol-->
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version>
</dependency>
小试一下:
//VM的详细情况
System.out.println(VM.current().details());
//所有的对象分配的字节数都是8的整数倍
System.out.println(VM.current().objectAlignment());
Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
类型指针只有4字节,而不是前面说的8字节的事,后面说。列的含义:
再用上面的Customer做测试:
class Customer{int id;boolean flag;
}
Customer customer = new Customer();
System.out.println(ClassLayout.parseInstance(customer).toPrintable());
可以发现无属性时,补齐到16字节,有属性时:
7、对象分代年龄
64位虚拟机下,对象分代年龄占4bit,也就是说,最大是1111,对应十进制就是15,这也是前面说的15次以后(到最大了)会从新生区到养老区的原因。MaxTenuringThreshold参数默认值就是15,在IDEA中添加JVM参数,改为16或更大:
//不同版本的Java不太一样,可改为17
-XX:MaxTenuringThreshold=16
再次运行,发现创建JVM都失败了:
8、压缩指针
在IDEA终端执行:
java -XX:+PrintCommandLineFlags -version
查看开启的一些参数:
发现这里使用了压缩指针,所以说前面看到类型指针只有4字节,而非理论里的8字节
从JDK 1.6开始,64位的JVM支持UseCompressedOops选项。其可对OOP(Ordinary Object Pointer,普通对象指针)进行压缩,使其只占用4个字节,以达到节约内存的目的。在JDK 8下,该选项默认启用。
-XX:+UseCompressedOops // 开启指针压缩
-XX:-UseCompressedOops // 关闭指针压缩
关闭压缩:
继续看new Object()对象的大小和分布,此时就是8+8了: