目录

一、概述

二、对象布局的总体结构

三、获取对象布局案例

四、Mark Word标记字详解

五、总结


一、概述

我们都知道,Java的对象存储在堆(Heap)内存,当我们创建一个对象时,对象内部具体存放了哪些信息,很多时候我们只关心如何创建一个对象,却忽略了其内部内存布局。

接下来我们就来探索一下Java对象头里边到底包含了哪几部分信息?了解对象内部结构,有助于我们理解Synchronized关键字的工作原理、对象晋升老年代年龄为什么是15、Java中锁升级过程等一些问题,这些都是和对象头息息相关。

注:本文笔者以JDK1.8 64位JVM为例。

二、对象布局的总体结构

假设现在有一个类Cal,当我们创建一个Cal类的实例对象时,JVM内存结构布局大概如下图所示:

从图中可以看到,new Cal()实例对象中的信息还是很多的,其中包括GC分代年龄、锁状态标识、类型指针等等。

总结一下,Object对象主要包括几个部分:

  • 对象头Object Header

对象头里面又包括:Mark Word标记字、类型指针Klass Word、数组长度

    1. Mark Word:标记字,包括哈希值(HashCode)、GC分代年龄、锁状态标志等信息。

① 哈希值:用于栈对堆空间中对象的引用指向,主要用在栈中局部变量找到堆中创建的实例对象;

② GC分代年龄:记录幸存者区对象被GC之后的年龄,一般年龄达到15之后下一次GC就会直接进入老年代中【为什么是15呢?因为GC分代年龄的长度是4位,4位的二进制最大就是1111,转换为十进制就是15】;

③ 锁状态标志:记录一些加锁的信息,锁升级过程跟它息息相关;

  • 2. 类型指针:Klass Word,是一个指向方法区中Class信息的指针,意味着该对象知道自己是哪个Class的实例,32位的JVM占用32位长度,64位的JVM占用64位长度。所以使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩;
  • 3. 数组长度:如果本对象是一个数组对象时,才会有这个部分,在32位JVM中占用32位,64位JVM中占用64位(8字节)的空间【开启指针压缩后缩小到32位】,里面保存着数组长度,注意非数组对象是没有这一部分的;
  • 实例数据

记录一个对象里面包含的属性信息;

  • 对齐填充

填充部分仅起到占位符的作用, 原因是JVM要求java的对象占的内存大小应该是8bit的倍数,假如不是,就采用对齐填充的方式将其补齐8字节整数倍,那么为什么是8呢?原因是64位机器能被8整除的效率是最高的。

上面文字描述不太方便记忆,笔者使用脑图总结一下:

三、获取对象布局案例

前面介绍了对象内部布局结构,下面我们通过程序输出对象内部结构来验证一下。

【a】添加jol输出对象布局的maven依赖

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version>
</dependency>

【b】调用ClassLayout.parseInstance().toPrintable()输出对象内部布局

package com.wsh.mybatis.mybatisdemo.entity;import org.openjdk.jol.info.ClassLayout;public class Cal {private int a = 1;public static void main(String[] args) {Cal cal = new Cal();//输出cal对象的布局System.out.println(ClassLayout.parseInstance(cal).toPrintable());}}

运行后输出结果如下:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)  //markword               01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header)  //markword               00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)  //klass pointer 类元数据  05 c2 00 20 (00000101 11000010 00000000 00100000) (536920581)12     4    int Cal.a                                     1    // Instance Data 对象实际的数据
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
  • OFFSET:偏移地址,单位字节;
  • SIZE:占用的内存大小,单位为字节;
  • TYPE DESCRIPTION:类型描述,其中object header为对象头;
  • VALUE:对应内存中当前存储的值;

如上图,我们看到对象头所占用的内存大小为12【4 + 4 + 4,就是三个(object header)】 * 8bit = 96bit,这是因为JDK8版本默认开启指针压缩的,开启指针压缩可以减少对象的内存使用。因此,开启指针压缩,理论上来讲,大约能节省百分之五十的内存。JDK8及以后版本已经默认开启指针压缩,无需配置。当然也可以可以通过如下jvm参数关闭指针压缩。

-XX:-UseCompressedOops  //关闭指针压缩     

下面来看一下数组对象的内存结构布局:

public class Cal {public static void main(String[] args) {int[] array = new int[10];//输出cal对象的布局System.out.println(ClassLayout.parseInstance(array).toPrintable());}}

输出如下:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header) //mark word              01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header) //mark word              00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header) //klass pointer 类元数据  6d 01 00 20 (01101101 00000001 00000000 00100000) (536871277)12     4        (object header) //数组长度                0a 00 00 00 (00001010 00000000 00000000 00000000) (10)16    40    int [I.<elements>                             N/A
Instance size: 56 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

对比前面Cal的内存布局,因为这里输出的是数组对象,可以看到,Object Header多出了32位长度,也就是说如果是数组对象,它的对象头所占用的内存大小为16【4 + 4 + 4 + 4,就是四个(object header)】 * 8bit = 128bit,多出的32位就是存放的数组长度。

四、Mark Word标记字详解

Mark Word主要用来存储对象自身的运行时数据,包括hashcode、GC分代年龄等。32位JVM的Mark word为32位,64位JVM为64位。本篇笔者使用的是64位jvm,所以mark word是64位。先来看一张图:

以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

  • lock:表示锁状态的标记位,占用2个二进制位;
  • biased_lock:表示对象是否启用偏向锁标记,占用1个二进制位。biased_lock为1时表示对象启用偏向锁,为0时表示对象没有偏向锁;

通过倒数三位数,即【biased_lock + lock】,我们可以判断出锁的类型,表达的锁状态含义如下表所示:

偏向锁标识(biased_lock)

锁标识(lock)

锁的类型

0

01

无锁

1

01

偏向锁

0

00

轻量级锁

0

10

重量级锁

0

11

GC标志

  • age:表示对象的分代年龄,占用4个二进制位。默认情况下,GC晋升到老年代的年阈值为15。由于age只有4位,表示的二进制最大也就是1111,转换成十进制也就是最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
  • identity_hashcode: 表示对象的hashcode,占用31个二进制位。
  • thread:表示持有偏向锁的线程ID。
  • epoch:表示偏向锁的时间戳。
  • ptr_to_lock_record:表示轻量级锁状态下,指向栈中锁记录的指针。
  • ptr_to_heavyweight_monitor:表示重量级锁状态下,指向对象监视器Monitor的指针。

下面通过一个案例,来看下加了锁之后的内存布局是怎么样的,通过对象头分析锁状态。

import org.openjdk.jol.info.ClassLayout;public class Cal {public static void main(String[] args) throws InterruptedException {Cal cal = new Cal();synchronized (cal) {Thread.sleep(1000);//输出cal对象的布局System.out.println(ClassLayout.parseInstance(cal).toPrintable());Thread.sleep(1000);}}}

输出结果如下:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           b8 f3 e9 02 (10111000 11110011 11101001 00000010) (48886712)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           05 c5 00 20 (00000101 11000101 00000000 00100000) (536921349)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

mark word是反向存储的,根据mark word是由前64位的value倒序拼接成的串组成,如下:

00000000 00000000 00000000 00000000 00000010 11101001 11110011 10111000

mark word标记头倒数三位是:000,倒数第三位为0,表示这不是偏量锁,倒数后2位为00,所以这是一把轻量锁;

五、总结

本篇文章主要介绍了Object对象的内存布局,包含对象头,实例数据、对齐填充等,并且通过示例打印出了对象的内存结构信息以及各种锁状态标识等,这其中涉及到锁升级的内容,后面专门写一篇文章总结锁升级相关知识。以上就是关于对象内部布局的一些学习总结,如有不对之处,还望指正,希望对大家有所帮助。

Object对象的内存布局学习总结相关推荐

  1. jvm学习笔记(2)——java对象的内存布局

    一.内存布局: 一个java对象在内存中可分为3个区域:对象头(Header).实例数据(Instance Data).对齐填充(Padding). 对象头(两部分): 对象自身运行时的数据.如哈希码 ...

  2. 实探java对象的内存布局

    实探java对象的内存布局 在我深入学习synchronized的时候,我查了很多资料,发现synchronized锁住的是对象的对象头,然后我又了解什么是对象头,这块的资料就很多了. 总结一下就是: ...

  3. C# CLR 聊聊对象的内存布局 一个空对象占用多少内存

    在 C# 中的对象大概可以分为三个不同的类型,包括值类型.引用类型和其他类型.本文主要讨论的是引用类型对内存空间的占用情况.在讨论开始之前我想问问大家,一个空的对象会占用多少内存空间?当然这个问题本身 ...

  4. 动态加载laydate 失效_Java对象的内存布局+反射的原理+动态代理+ 并发和锁+文末彩蛋...

    # 一行代码是怎么运行的 首先,java代码会被编译成字节码,字节码就是java虚拟机定义的一种编码格式,需要java虚拟机才能够解析,java虚拟机需要将字节码转换成机器码才能在cpu上执行. 我们 ...

  5. JVM从入门到精通(四):内存屏障与JVM指令,对象的内存布局

    JMM 硬件层数据一致性 协议很多,intel 用MESI https://www.cnblogs.com/z00377750/p/9180644.html 现代CPU的数据一致性实现 = 缓存锁(M ...

  6. JVM——深入分析对象的内存布局

    概述 一个对象本身的内在结构需要一种描述方式,这个描述信息是以字节码的方法存储在方法区中的. Class 本身就是一个对象,都以 KB 为单位,如果 new Integer() 为了表示一个数据就占用 ...

  7. java占位符填充_程序员:深入理解Java虚拟机,对象的内存布局

    在 HotSpot 虚拟机中,对象在内存中存储的布局分为 3 块区域:对象头 ( Header ) .实例数据 ( InstanceData ) 和对齐填充 (Padding) . 一.对象的内存布局 ...

  8. Java对象创建的过程及对象的内存布局与访问定位

    这里以HotSpot为例,且所说的对象指普通的Java对象,不包括数组和Class对象等. 1.对象创建的过程 1.类加载.解析.初始化:虚拟机遇到new时先检查此指令的参数是否能在常量池中找到类的符 ...

  9. java对象实例化内存布局与访问定位

    文章目录 1. 对象实例化方式 2. 对象创建的步骤 3. 对象的内存布局 4. 对象的访问定位 1. 对象实例化方式 通过new的方式,例如 Persion p = new Persion() 通过 ...

  10. 欧尼酱讲JVM(18)——对象实例化内存布局与访问定位

    目录 对象的实例化 创建对象的几种方式 从字节码角度看对象的创建过程 对象创建的六个步骤 第一步:判断对象对应的类是否加载.链接.初始化 第二步:为这个对象分配内存 第三步:处理并发安全问题 第四步: ...

最新文章

  1. PICRUSt2分析实战:16S扩增子OTU或ASV预测宏基因组EC、通路、KO(200806更新)
  2. leetcode 790. Domino and Tromino Tiling | 790. 多米诺和托米诺平铺(暴力递归->DP)
  3. android volley 上传图片 和参数,Android使用Volley实现上传文件功能
  4. java ssl发送邮件_通过SSL发送的Java邮件
  5. javascript 排序_JavaScript中的排序方法
  6. 实现文件中名词的统计计数_通过勤哲EXCEL和Excel中的rank函数实现排名统计
  7. leetcode 寻找两个有序数组的中位数
  8. [LeetCode] Z字型变换
  9. 开源,如何以商业化模式打造万亿企业应用市场?
  10. Audio PCM输出流程(三十三)
  11. python文件操作
  12. PMP知识要点详细汇总
  13. 说程序员吃青春饭的两种人:一种是外行,一种是这2样东西没学好
  14. Navicat 15.0.27 激活时弹出No All Pattern Found File Already Patched?(已解决)
  15. 绝对最大额定值(ABSOLUTE MAXIMUM RATINGS: ABS)是否可以超过
  16. [MRI] 核磁共振T1和T2图像
  17. 《SQL 入门教程》示例数据库
  18. 想学大数据?先看完这几本书再说
  19. 使用PADS绘制排线的细节笔记
  20. 使用多线程时@Service工具类出现NullPoint错误解决

热门文章

  1. 正态分布下贝叶斯决策的引入
  2. linux cpu mysql_Linux 指定MySQL服务运行的CPU核心(数)
  3. 公式推导以及代码书写 11-26
  4. OFDM简介--OFDM的发送(1)
  5. python后端和爬虫_【后端开发】python爬虫难学吗
  6. 并查集路径压缩_并查集专题
  7. IDEA连接服务器执行python程序
  8. 计算机网络超详细笔记(四):介质访问控制子层
  9. 【状压dp】【POJ2288】Islands and Bridges【Hamilton路】
  10. BurpSuite Pro 2021.2 最新版本