对象布局 #
heap (where): new (eden ,s0 ,s1) ,old, metaspace
对象的构成元素(what) HotSpot虚拟机里,对象在堆内存中的存储布局分为三个部分
- 对象头(Header)
- 对象标记 MarkWord
- 类元信息(类型指针 Class Pointer,指向方法区的地址)
- 对象头多大 length(数组才有)
- 实例数据(Instance Data)
- 对其填充(Padding,保证整个对象大小,是8个字节的倍数)
- 对象头(Header)
对象头 #
对象标记
- Object o= new Object(); //new一个对象,占内存多少
- o.hashCode() //hashCode存在对象哪个地方
- synchronized(o){ } //对象被锁了多少次(可重入锁)
- System.gc(); //躲过了几次gc(次数)
上面这些,哈希码、gc标记、gc次数、同步锁标记、偏向锁持有者,都保存在对象标记里面
- 如果在64位系统中,对象头中,**mark word(对象标记)**占用8个字节(64位);**class pointer(类元信息)**占用8个字节,总共16字节(忽略压缩指针)
- 无锁的时候,
类型指针 注意下图,指向方法区中(模板)的地址
实例数据和对齐填充 #
实例数据
用来存放类的属性(Filed)数据信息,包括父类的属性信息
对齐填充
填充到长度为8字节,因为虚拟机要求对象起始地址必须是8字节的整数倍(对齐填充不一定存在)
示例
class Customer{ int id;//4字节 boolean flag=false; //1字节 } //Customer customer=new Customer(); //该对象大小:对象头(对象标记8+类型指针8)+实例数据(4+1)=21字节 ===> 为了对齐填充,则为24字节
源码查看 #
具体的(64位虚拟机为主) #
- 无锁和偏向锁的锁标志位(最后2位)都是01
- 无锁的倒数第3位,为0,表示非偏向锁
- 偏向锁的倒数第3位,为1,表示偏向锁
- 轻量级锁的锁标志位(最后2位)是00
- 重量级锁的锁标志位(最后2位)是10
- GC标志(最后2位)是11
如上所示,对象分代年龄4位,即最大值为15(十进制)
源码中
使用代码演示上述理论(JOL) #
<!--引入依赖,用来分析对象在JVM中的大小和分布-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
//使用
//VM的细节详细情况
System.out.println(VM.current().details());
//所有对象分配字节都是8的整数倍
System.out.println(VM.current().objectAlignment());
/* 输出:
# 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]
8
*/
简单的情形 注意,下面的8 4 (object header: class) 0xf80001e5,由于开启了类型指针压缩,只用了4个字节
public class Hello4 { public static void main(String[] args) throws InterruptedException { Object o = new Object(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); //16字节 Customer customer = new Customer(); System.out.println(ClassLayout.parseInstance(customer).toPrintable()); //16字节 } } class Customer{ } /*输出 java.lang.Object object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0xf80001e5 12 4 (object alignment gap) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total com.ly.Customer object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0xf800cc94 12 4 (object alignment gap) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total Process finished with exit code 0 */
带有实例数据
public class Hello4 { public static void main(String[] args) throws InterruptedException { Customer customer = new Customer(); System.out.println(ClassLayout.parseInstance(customer).toPrintable()); //16字节 } } class Customer{ private int a; private boolean b; } /*输出 com.ly.Customer object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0xf800cc94 12 4 int Customer.a 0 16 1 boolean Customer.b false 17 7 (object alignment gap) Instance size: 24 bytes Space losses: 0 bytes internal + 7 bytes external = 7 bytes total */
java 运行中添加参数 -XX:MaxTenuringThreshold = 16 ,则会出现下面错误,即分代gc最大年龄为15
压缩指针的相关说明
使用 java -XX:+PrintComandLineFlags -version ,打印参数
其中有一个, -XX:+UseCompressedClassPointers ,即开启了类型指针压缩,只需要4字节
当使用了类型指针压缩(默认)时,一个无任何属性对象是 8字节(markWord) + 4字节(classPointer) + 4字节(对齐填充) = 16字节
下面代码,使用了
-XX:-UseCompressedClassPointers
进行关闭压缩指针 一个无任何属性对象是 8字节(markWord) + 8字节(classPointer) = 16字节public class Hello4 { public static void main(String[] args) throws InterruptedException { Object o = new Object(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); //16字节 //16字节 } } /*输出 java.lang.Object object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 8 (object header: class) 0x000000001dab1c00 Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total */