承前启后,Java对象内存布局和对象头

大家好,我是小高先生。在我之前的一篇文章《并发编程防御装-锁(基础版)》中,我简要介绍了锁的基础知识,并解释了为什么Java中的任何对象都可以作为锁。在那里,我提到了对象头中有一个指向ObjectMonitor的指针,但没有深入探讨Java对象的内存结构。本文将引导大家深入了解Java对象的内存布局以及对象头结构,帮助大家更好地理解Java中的对象和锁,并为之后学习synchronized和锁升级打下基础。

  • new Object()怎么理解?
  • JOL
  • GC分代年龄

new Object()怎么理解?

平时我们开发的时候经常要new一个对象,很少想过这个对象存在哪里以及这个对象是有什么构成的,所以关于new Object的理解可归于两点:

  1. 位置所在(where)

学过JVM的同学都应该知道JVM内存结构,我们new出来的对象就在堆区,大对象在老年代,小对象在新生代。

  1. 构成布局(what)

1.对齐填充

先讲一下对齐填充,这个比较简单。HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是对象的大小是8字节的整数倍。对象头已经被设计成8字节的倍数,通常是1倍或2倍,如果实例数据大小不是8字节的整数倍,就需要用对齐填充添加一下,使对象大小为8字节的整数倍。

2.对象头

对象头是对象的另一个重要组成部分,它包含了一些关于对象的元信息。具体来说,对象头包括Mark Word和类元信息(类型指针)。

Mark Word

平时写代码的时候可能会通过hashcode()方法获取对象的哈希码,那哈希码保存在哪里呢?还有JVM中垃圾回收机制里说过,当一个对象在新生代经过15次Minor GC之后还存活下来就会进入老年代,那对象怎么记录GC次数呢?这些信息都记录在Mark Word中,先不需要都知道里面的标记代表什么,后续会讲到,这里只需要知道Mark Word存储了对象的一些重要信息。

存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 重量级锁定
空,不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

在64位系统中,Mark Word占8字节,类型指针占8字节,一共16字节。所以当我们new一个对象,还没有实例数据,那这个对象就是16字节。64为系统下,Mark Word就是64bit大小,存储结构如下:

比如现在对象是无锁状态,那Mark Word前25bit不用,之后的31bit存hashCode,偏向锁标志位为1,锁标志位为01,其他锁状态会在相关文章中讲解。

Mark Word默认存储hashCode、分代年龄和锁标志位等相关信息,这些信息都是与对象自身定义无关的数据,会根据对象的状态复用自己的存储空间,运行期间数据会随着锁状态而改变

类元信息(类型指针)

Phone ph = new Phone(),创建一个对象,会在JVM方法区存放对象的Klass类元信息,对象头的类型指针就是这个对象指向它的类元信息的指针,JVM通过类型指针确定这个对象是哪个类的实例,比如我创建Animal类,new Animal()实例的类型指针就会指向方法区中的Animal的Klass类元信息。由于Animal里面没有创建属性,所以Animal对象还没有实例数据,大小为16字节。

public class ObjectHeadDemo {
public static void main(String[] args) {
Animal a = new Animal();
}
}
class Animal{ }

我给大家百度了Klass类元信息具体是什么,因为一开始我也不懂。

Klass类元信息是JVM中表示类的元信息的数据结构

JVM中,每个Java类都对应一个Klass实例,是用C++编写的一个类,存储了Java类的所有元信息,包括属性、方法、修饰符等。Klass模型是JVM内部使用的一个概念,是Java类的内部表示。这个模型包含了类的所有信息,如类的完整名称、访问修饰符、父类、实现的接口、属性和方法等。这些信息在类加载的过程中被读取并存储到方法区。

这么看起来,Klass就想是这个类的模板。

3.实例数据

class Animal{
int id;
boolean flag = false;
}

如果创建了一个Animal对象,大小就是对象头(16字节)+ int(4字节)+ boolean(1字节)= 21字节,需要有对齐填充,21 + 3 = 24字节。

JOL

有关Java对象布局的理论知识已经学完了,那能不能从代码层面验证一下对象的结构呢。

JOL(Java Object Layout)是一个专门用于分析Java虚拟机(JVM)中对象内存布局的工具箱。它能够提供非常精确的关于对象如何分布在JVM内存中的信息。

1.先在POM文件中导入坐标

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

2.通过指令查看JVM详细细节

public class JOLDemo {
public static void main(String[] args) {
//VM的详细细节
System.out.println(VM.current().details());
//获取当前JVM实例的对象对齐方式
System.out.println(VM.current().objectAlignment()); }
}
class Customer{
int id;
boolean flag = false;
}

Object alignment指的是对象在内存中的对齐方式。8 bytes表示对象的大小必须是8的倍数,即对象的起始地址必须是8的整数倍。

我们创建一个Object类,看看这个类具体的结构是什么样的,通过ClassLayout.parseInstance(o).toPrintable()来查看。

public class JOLDemo {
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}

参数 作用
OFFSET 偏移量,也就是这个字段起始位置
SIZE 字节大小
TYPE Class中定义的类型
DESCRIPTION 类型描述
VALUE 是TYPE在内存中的值

我们这里会发现类型指针只占了4字节,之前说的是占8字节,这是为什么?这个我们稍后讨论。

另外一个细节会发现Instance size为16字节,因为对象头不够8的倍数,要补齐,也就是new一个对象默认大小为16字节。刚刚是创建了一个Object对象,现在再创建一个自己写的类看看结果,并且类中没有实例数据。结果表明自己写的类和Object类new出来的对象内存布局是一样的。

public class JOLDemo {
public static void main(String[] args) {
Object o = new Object();
//System.out.println(ClassLayout.parseInstance(o).toPrintable()); Customer c = new Customer();
System.out.println(ClassLayout.parseInstance(c).toPrintable());
}
}
class Customer{
//只有对象头,没有实例数据
}

另外一种情况是类中有实例数据,看看结果,这次除了对象头以外多了实例数据,然后也是用对齐填充补到8的倍数。

public class JOLDemo {
public static void main(String[] args) {
Object o = new Object();
//System.out.println(ClassLayout.parseInstance(o).toPrintable()); Customer c = new Customer();
System.out.println(ClassLayout.parseInstance(c).toPrintable());
}
}
class Customer{
int id;
String name;
}

现在来研究研究为什么类型指针是4而不是8,这是因为压缩指针的影响。使用java -XX:+PrintCommandLineFlags -version指令,启动Java程序的时候把所有参数打印出来。-XX:+UseCompressedClassPointers这个参数就表示使用了压缩指针,默认开启,结果就导致类型指针是4字节。



试试不开启指针压缩是什么样,正常工作中是不会用到的,这里就是跟大家尝试一下,看看结果而已。

GC分代年龄

GC年龄在Mark Word中用4bit存储,最大为15,也就是JVM参数-XX:MaxTenuringThreshold = 15。我们也可以验证一下,调整参数改成16试试,运行时是有异常的。

总结

本文和朋友们一起学习Java对象内存布局的知识,对象由对象头、实例数据和对齐填充组成。对象头包括Mark Word和类型指针,Mark Word记录一些对象的关键信息,主要包括锁的状态,类型指针是可以确定这个对象是哪个类的实例。JVM要求对象大小是8字节的整数倍,所以当对象头和实例数据大小不是8字节整数倍的时候,就需要对齐填充帮忙补齐。

东西不多但是挺重要的,可以更好地理解之前讲的synchronized底层分析,也可以对未来要学的锁升级做好准备。

承前启后,Java对象内存布局和对象头的更多相关文章

  1. HotSpot虚拟机对象探秘(对象创建,对象内存布局,对象访问定位)

    以常用的HotSpot虚拟机和JAVA内存区域堆为例,探讨对象的创建,对象的内存布局以及对象的访问定位 一.对象的创建 1)类加载:虚拟机遇到一条new指令时,先检测这个指令的参数能否在常量池中定位到 ...

  2. JVM之对象创建、对象内存布局、对象访问定位

    对象创建 类加载过后可以直接确定一个对象的大小 对象栈上分配是通过逃逸分析判定.标量替换实现的,即把不存在逃逸的对象拆散,将成员变量恢复到基本类型,直接在栈上创建若干个成员变量 选择哪种分配方式由Ja ...

  3. 浅谈Java虚拟机内存中的对象创建,内存布局,访问定位

    参考于 深入理解Java虚拟机 这里介绍HotSpot虚拟机(自带的虚拟机) 1.对象的创建 对于程序员来说,创建对象的方法: User user1 = new User(); User user2 ...

  4. Java单个对象内存布局.md

    我们在如何获取一个Java对象所占内存大小的文章中写了一个获取Java对象所占内存大小的工具类(ObjectSizeFetcher),那么接下来,我们使用这个工具类来看一下Java中各种类型的对象所占 ...

  5. Java对象内存布局

    本文转载自Java对象内存布局 导语 首先直接抛出问题 Unsafe.getInt(obj, fieldOffset)中的fieldOffset是什么, 类似还有compareAndSwapX(obj ...

  6. 图文详解Java对象内存布局

    作为一名Java程序员,我们在日常工作中使用这款面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了.对象的创建方式虽然有很多,可以通过new.反射.clone.反序列化等不同方式来创建 ...

  7. JVM-对象及对象内存布局

    目录 前言 类与对象 对象类二分模型 对象 对象内存布局 JOL工具 对象头 Mark Word 类型句柄 对象头与锁膨胀 无锁 偏向锁 轻量级锁 重量级锁 重量级锁降级 实例数据 填充 对象生命周期 ...

  8. JVM 系列(4)一看就懂的对象内存布局

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  9. 图说C++对象模型:对象内存布局详解

    0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...

  10. c++ 对象内存布局详解

    今天看了的,感觉需要了解对象内存的问题.参考:http://blog.jobbole.com/101583/ 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个 ...

随机推荐

  1. FileZilla 连接不上宝塔

    1,修改 pureftp的配置文件 ForcePassiveIP 为服务器的ip , 并去掉 # 2,FileZilla 使用明文连接

  2. [转帖]GB18030 编码

    https://www.qqxiuzi.cn/zh/hanzi-gb18030-bianma.php GB18030编码采用单字节.双字节.四字节分段编码方案,具体码位见下文.GB18030向下兼容G ...

  3. [转帖]Kafka中Topic级别配置

    https://www.cnblogs.com/moonandstar08/p/6139502.html 一.Kafka中topic级别配置 1.Topic级别配置 配置topic级别参数时,相同(参 ...

  4. [转帖]【MySQL 8】MySQL 5.7都即将停只维护了,是时候学习一波MySQL 8了!

    https://juejin.cn/post/7111255789876019208 MySQL 8新特性 选择MySQL 8的背景:MySQL 5.6已经停止版本更新了,对于 MySQL 5.7 版 ...

  5. [转帖]chrome历史版本及重大变化(维基百科)

    Google Chrome是Google LLC开发的免费 网络浏览器.开发过程分为不同的"发布渠道",每个发布渠道都在单独的开发阶段进行构建.Chrome提供了4种渠道:稳定版, ...

  6. HTTPS下tomcat与nginx的前端性能比较

    HTTPS下tomcat与nginx的前端性能比较 摘要 之前比较http的web服务器的性能. 发现nginx 比 tomcat 要好 50% 然后想到, https的情况下不知道两者有什么区别 所 ...

  7. [译]深入了解现代web浏览器(一)

    本文是根据Mariko Kosaka在谷歌开发者网站上的系列文章https://developer.chrome.com/blog/inside-browser-part1/ 翻译而来,共有四篇,该篇 ...

  8. SAP PO7.5 有关https 接口body编码格式 application/x-www-form-urlencoded

    近期项目中,在PO中做接口 遇到OAUTH2.0认证方式,token获取过程中编码格式为 "application/x-www-form-urlencoded" 实现过程错误记录: ...

  9. 【图论,网络流】CF1525F Goblins And Gnomes

    Problem Link 你在打怪.你有一个 \(n\) 个点 \(m\) 条边的 DAG,接下来会有 \(k\) 波怪来袭,第 \(i\) 波怪有 \(i\) 个,它们会各自选择走一条路径,要求它们 ...

  10. Gin 框架之jwt 介绍与基本使用

    目录 一.JWT 介绍 二.JWT认证与session认证的区别 2.1 基于session认证流程图 2.2 基于jwt认证流程图 三. JWT 的构成 3.1 header : 头部 3.2 pa ...