实探java对象的内存布局

在我深入学习synchronized的时候,我查了很多资料,发现synchronized锁住的是对象的对象头,然后我又了解什么是对象头,这块的资料就很多了。
总结一下就是:
在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充数据。
对象头:对象头主要结构是由Mark Word 和 Class Metadata Address

实例数据:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
之后再就是详细的介绍对象头占多大,每个字节代表啥意思等等。

说了一堆,我知道了synchronized的锁的原理,知道了对象头的作用,知道了对象在内存中的布局。总之就是理论的东西知道了不少。但是我还是想实际看看对象在内存中的布局到底是啥样的,然后我又找啊找,终于让我找到了。

首先引入这个依赖

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

第一次

我新建了一个空对象O

public class O {}

然后将对象内存布局结构打印出来

    public static void main(String[] args) {O o = new O();//获取对象内存布局String s = ClassLayout.parseInstance(o).toPrintable();System.out.println(s);}

下面是打印出来的结果

OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现了对象头是12个字节,总大小是16字节,空对象没有属性,方法,所以实例数据是0字节,有4字节的数据填充,验证了虚拟机要求对象起始地址必须是8字节的整数倍。

对象头 12 byte
实例数据 0 byte
填充数据 4 byte
对象大小 16 byte

第二次

新建一个对象F。

public class F {int f = 0;
}

对象O继承对象F,里面包含静态属性,非静态属性,数组,引用对象,方法。

public class O extends F{int i; // 非静态属性int[] arr = new int[5]; //数组Object object = new Object(); //相当于属性static int b = 5; //静态属性private void a(){int a = 0;}
}

打印出来的结果

OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 81 c1 00 f8 (10000001 11000001 00000000 11111000) (-134168191)
12 4 int F.f 0 //父类属性
16 4 int O.i 0 //非静态属性
20 4 int[] O.arr [0, 0, 0, 0, 0] //非静态属性数组(4bit,包含了数组的长度)
24 4 java.lang.String O.s (object) //引用类型属性(4bit)
28 4 java.lang.Object O.object (object) //引用类型属性(4bit)
Instance size: 32 bytes //总大小32byte(正好是8的整数倍,没有填充数据)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

验证了 实例数据是存放类的非静态属性数据信息(引用类型都是4字节),包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。不保存静态属性和方法信息。

对象头 12 byte
实例数据 20 byte
填充数据 0 byte
对象大小 32 byte

从这两次就可以看出来

  • 对象头总是占用12个字节(这里也有的人说占用了16个字节,是因为Class Metadata Address默认开启了指针压缩,没有开启就是16个字节,涉及到硬件的设计了,这里就不说了,感兴趣的朋友自己搜索看看。)
  • 实例数据没有就不占用字节
  • 总字节是8的倍数,不够数据填充补足
  • 实例数据存放的非静态成员变量

这样我们的知识就不是只停留在理论阶段了,关于对象的内存布局自己可以亲自动手看看到底是啥样的!

实例数据和对象填充其实这里就没有什么好说的,通过上面的演示大家应该知道了是什么意思,那么这里最重要的就是对象头了。

对象头

对象头:对象头主要结构是由Mark Word 和 Class Metadata Address
这是我从其他的文章里面看到的,但是我现在想去验证一下,所以我去查看了一下 OpenjdK关于hotSpot的文档


这个相当于JVM规范中定义的对象头的概念,翻译一下
object header(对象头)

每个GC管理的堆对象开头的通用结构。 (每个oop都指向一个对象头。)包括有关堆对象的布局,类型,GC状态,同步状态和标识哈希码的基本信息。 由两个词组成。 在数组中,紧随其后的是长度字段。 请注意,Java对象和VM内部对象都具有通用的对象标头格式。

mark word

每个对象头的第一个部分。 通常,一组位域包括同步状态和标识哈希码。 也可以是指向与同步相关的信息的指针(具有特征性的低位编码)。 在GC期间,可能包含GC状态位。

klass pointer

每个对象头的第二个部分。 指向另一个对象(元对象),该对象描述原始对象的布局和行为。 对于Java对象,“容器”包含C ++样式“ vtable”。

放在谷歌翻译里面翻译的,原谅我英文比较差,就不润滑翻译了。
到这里我就稍微了解了下,对象头分为两部分,第一部分是mark word,存放的同步状态,哈希码,与同步相关的指针,GC状态。第二部分是klass pointer至于为什么其他文章写的是Class Metadata Address我也就不深究了,因为我也不知道,但是意思上还是都差不多的,类的指针和类的元数据地址,都是代表的是指向这个对象所在类的地址。

上面的东西说白了还是概念,只不过是官方的权威的概念,接下来我还是想看看对象头的具体实现。包括哪些数据存放哪些东西。所以我下载了OpenJDK的源码来找找代码。
至于怎么下载openjdk的源码,我从openjdk的官方下载禁止下载,然后到github上搜一下openjdk就好了。


在markOop.hpp(对应的就是mark word)文件中,分为32位JVM和64位JVM
从上图可以知道32bit的 占用32bit,

hash 25bit
age(GC分代年龄) 4bit
biased_lock(偏向锁) 1bit
lock 2bit

64bit的占用64bit,具体的还是看上图。
看到这里想到以前看过的一个面试题,每发生一次young GC,存活的对象分代年龄增加1,到多少的时候会进入老年代,答案是15,为什么是15,不能是16吗?如果以前肯定是懵逼的,我哪儿知道为什么是15,今天应该就知道了,从上面知道对象头中保存的分代年龄,不管32位还是64位的虚拟机都是4bit, 最大值是1111,即16,保存的范围是0~15,所以分代年龄是15。当然如果后面虚拟机变更成age 用5位表示,那对象的分代年龄最大就可以达到31次。


我们这里使用的是64的JVM,所以对象头的mark word是占用了64bit即8个字节,上面图中对象头总共是12个字节,那么剩下的4个字节就是保存klass pointer即指向类的信息的指针。
mark word :64bit
klass pointer : 32bit
这里解释一下,指针是需要用64位来表示的,但是由于64位太耗内存,所以采用了指针压缩的方法,来用32位表示64位的地址。其他的涉及到的东西太底层,不是咱们讨论的东西,感兴趣的朋友可以网上随便搜索下指针压缩,大量的文章让你借鉴参考。

mark word

接下来就是重点看看mark word中保存的东西,在不同状态下都是如何表示的。
对象在不同状态下mark word表示的含义是不同的。
对象总共有5种状态:
无状态(刚 new 出来),偏向锁,轻量级锁,重量级锁,gc(对象要被回收)
下图是对象不同状态下mark word表示的含义

这里对象的状态有5种,但是lock是占用2位,总共能表示4种状态 即00,01,10,11。所以这里(biased_lock)偏向锁联合lock来表示5种状态。

我们这里来看下无锁状态的mark word
从上面的图中我们知道无锁状态下的mark word
unsed:25 | hashcode:31| unsed:1 | age:4 | biased_lock:1 | lock:2
下面我们实际证明一下

    public static void main(String[] args) {O o = new O();//计算对象的hashcode值,16进制表示System.out.println(Integer.toHexString(o.hashCode()));//获取对象内存布局String s = ClassLayout.parseInstance(o).toPrintable();System.out.println(s);}

实际输出的mark word

理论的mark word
unsed:25 | hashcode:31| unsed:1 | age:4 | biased_lock:1 | lock:2
实际的mark word
(00000001 00101110 01001100 01011111)
(01101000 00000000 00000000 00000000)
根据理论说unsed:25 ,意思前25位应该都是0,但是和实际的不一样,我陷入了懵逼,后来查阅了资料发现,我用的笔记本是微软的架构,存储是小端存储,说白了就是倒着存(不是每一位倒着存,是每一个字节倒着存),至于为啥我也不知道,太深奥了,我不想知道为啥,我只想验证下。所以我们就倒着看,发现后25位确实是0,

unsed:25 hashcode:31 unsed:1 age:4 biased_lock:1 lock:2
黄色 绿色 红色 蓝色 灰色 粉红色

我上面的实际输出图中输出了hashcode,和这里比对的值是一样的,说明mark word的存储确实是这样子的。

文章就先讲到这里了,关于对象其他状态下的验证,大家可以自行去试试。

实探java对象的内存布局相关推荐

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

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

  2. java对象的内存布局

    java对象内存布局简介 java对象的内存布局包含对象头.实例数据.对齐填充 对象头 markword:对象默认的hash码.分代年龄,锁的状态标识等. class point:指向对象对应的类的元 ...

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

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

  4. 《深入理解java虚拟机》读书笔记:Java对象的内存布局

    一个int类型4占4个字节的内存,一个byte一个字节.但是他们的封装类型Integer,Byte对象内存损耗还是一样的吗?并不是,而且差距十分大. HotSpot虚拟机中,一个普通的Java对象由3 ...

  5. 【Java】Java 如何查看对象的内存布局

    1.概述 在一些博客中说java的内存布局是分为3个部分的,那么我们怎么确认这个呢?或者如何打印java 对象的内存布局呢? 下面介绍可以使用这个类 首先引入这个包 <dependency> ...

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

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

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

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

  8. java怎么限制一个对象的内存_java对象的内存布局及创建过程

    一.对象的内存布局 对象的内存结构又可以被分为:对象头,实例数据,对象填充 对象头:对象头结构在32位JVM与64位JVM中的实现细节是不同的 32bit: 64bit: 实例数据:对象真正存储的有效 ...

  9. Java 对象占用内存大小

    Java 对象 如果想要了解java对象在内存中的大小,必须先要了解java对象的结构. HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header).实例数据(Instan ...

最新文章

  1. 批量移动某目录下某类型的文件到指定的目录下
  2. Spring Boot微服务,Docker和Kubernetes研讨会–第2部分
  3. 使用Excel和TF实现Transformer!
  4. Linux全攻略--DHCP服务器配置与管理
  5. 1129 Recommendation System
  6. [IDDFS+背包] 洛谷P2744 [USACO5.3]量取牛奶Milk Measuring
  7. TCP/IP报文头部结构
  8. mysql之为表添加一个字段并设定默认值
  9. html5 dom 结构,HTML5 DOM扩展
  10. 君正T31 ACC解码
  11. Java server sent_Server-Sent Events的Java简单实现
  12. 无法解析类型 javax.swing.JComponent。从必需的 .class 文件间接引用了它
  13. HTML5期末大作业:蛋糕甜品网站设计——蛋糕甜品店铺(11页) HTML+CSS+JavaScript 关于美食甜品的HTML网页设计
  14. 原生JS实现记忆翻牌小游戏
  15. 17家中国初创IT公司的失败史
  16. VMware三种网络模式配置详解。
  17. ES5, ES2015 和 TypeScript 的区别
  18. Secure CRT连接华三模拟器和华为模拟器(CRT通过pipe连接华三模拟器)
  19. fx5u模拟量如何读取_三菱FX5U PLC内置模拟量输入为电流怎么设置?
  20. G1垃圾回收日志分析

热门文章

  1. CSS中的绝对定位和相对定位
  2. PHP代码审计——任意文件删除漏洞(YXcms)
  3. ASEMI代理ST/意法STW78N65M5原厂渠道汽车级MOSFET
  4. Python中的布尔类型
  5. 苹果CMS完全开发文档 - 苹果CMS手册 - 苹果CMS教程 - 苹果CMS帮助 - 苹果cmsV10
  6. 比较两个结构体是否相等
  7. Acwing 823.排列
  8. spring中的loc和aop
  9. 计算机打音乐两只老虎,两只老虎(音乐、汇编程序)
  10. Jetson TX2 挂载SATA SSD 并设置为启动盘