Object对象的内存布局学习总结
目录
一、概述
二、对象布局的总体结构
三、获取对象布局案例
四、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、数组长度
- 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对象的内存布局学习总结相关推荐
- jvm学习笔记(2)——java对象的内存布局
一.内存布局: 一个java对象在内存中可分为3个区域:对象头(Header).实例数据(Instance Data).对齐填充(Padding). 对象头(两部分): 对象自身运行时的数据.如哈希码 ...
- 实探java对象的内存布局
实探java对象的内存布局 在我深入学习synchronized的时候,我查了很多资料,发现synchronized锁住的是对象的对象头,然后我又了解什么是对象头,这块的资料就很多了. 总结一下就是: ...
- C# CLR 聊聊对象的内存布局 一个空对象占用多少内存
在 C# 中的对象大概可以分为三个不同的类型,包括值类型.引用类型和其他类型.本文主要讨论的是引用类型对内存空间的占用情况.在讨论开始之前我想问问大家,一个空的对象会占用多少内存空间?当然这个问题本身 ...
- 动态加载laydate 失效_Java对象的内存布局+反射的原理+动态代理+ 并发和锁+文末彩蛋...
# 一行代码是怎么运行的 首先,java代码会被编译成字节码,字节码就是java虚拟机定义的一种编码格式,需要java虚拟机才能够解析,java虚拟机需要将字节码转换成机器码才能在cpu上执行. 我们 ...
- JVM从入门到精通(四):内存屏障与JVM指令,对象的内存布局
JMM 硬件层数据一致性 协议很多,intel 用MESI https://www.cnblogs.com/z00377750/p/9180644.html 现代CPU的数据一致性实现 = 缓存锁(M ...
- JVM——深入分析对象的内存布局
概述 一个对象本身的内在结构需要一种描述方式,这个描述信息是以字节码的方法存储在方法区中的. Class 本身就是一个对象,都以 KB 为单位,如果 new Integer() 为了表示一个数据就占用 ...
- java占位符填充_程序员:深入理解Java虚拟机,对象的内存布局
在 HotSpot 虚拟机中,对象在内存中存储的布局分为 3 块区域:对象头 ( Header ) .实例数据 ( InstanceData ) 和对齐填充 (Padding) . 一.对象的内存布局 ...
- Java对象创建的过程及对象的内存布局与访问定位
这里以HotSpot为例,且所说的对象指普通的Java对象,不包括数组和Class对象等. 1.对象创建的过程 1.类加载.解析.初始化:虚拟机遇到new时先检查此指令的参数是否能在常量池中找到类的符 ...
- java对象实例化内存布局与访问定位
文章目录 1. 对象实例化方式 2. 对象创建的步骤 3. 对象的内存布局 4. 对象的访问定位 1. 对象实例化方式 通过new的方式,例如 Persion p = new Persion() 通过 ...
- 欧尼酱讲JVM(18)——对象实例化内存布局与访问定位
目录 对象的实例化 创建对象的几种方式 从字节码角度看对象的创建过程 对象创建的六个步骤 第一步:判断对象对应的类是否加载.链接.初始化 第二步:为这个对象分配内存 第三步:处理并发安全问题 第四步: ...
最新文章
- PICRUSt2分析实战:16S扩增子OTU或ASV预测宏基因组EC、通路、KO(200806更新)
- leetcode 790. Domino and Tromino Tiling | 790. 多米诺和托米诺平铺(暴力递归->DP)
- android volley 上传图片 和参数,Android使用Volley实现上传文件功能
- java ssl发送邮件_通过SSL发送的Java邮件
- javascript 排序_JavaScript中的排序方法
- 实现文件中名词的统计计数_通过勤哲EXCEL和Excel中的rank函数实现排名统计
- leetcode 寻找两个有序数组的中位数
- [LeetCode] Z字型变换
- 开源,如何以商业化模式打造万亿企业应用市场?
- Audio PCM输出流程(三十三)
- python文件操作
- PMP知识要点详细汇总
- 说程序员吃青春饭的两种人:一种是外行,一种是这2样东西没学好
- Navicat 15.0.27 激活时弹出No All Pattern Found File Already Patched?(已解决)
- 绝对最大额定值(ABSOLUTE MAXIMUM RATINGS: ABS)是否可以超过
- [MRI] 核磁共振T1和T2图像
- 《SQL 入门教程》示例数据库
- 想学大数据?先看完这几本书再说
- 使用PADS绘制排线的细节笔记
- 使用多线程时@Service工具类出现NullPoint错误解决