资讯专栏INFORMATION COLUMN

Java对象内存布局解析

charles_paul / 2723人阅读

摘要:堆内存用于存放我们在程序中创建的对象,一旦没有足够的空间用于存放这些对象,即会抛出异常。当我们采用后一种方式时,我们需要了解一个对象是如何占据堆内存空间的,或者说是了解一个对象是由哪些部分组成的。

JVM将内存划分为程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、堆(Heap)以及方法区(Method Area)。作为开发者,我们最关注的是虚拟机栈以及堆这两块区域。虚拟机栈所需要的内存空间在编译期间即可明确,而堆内存所需要的空间需要在运行时才可确定。堆内存用于存放我们在程序中创建的对象,一旦没有足够的空间用于存放这些对象,即会抛出OutOfMemoryError异常。在这种情况下,我们可以调整堆内存的大小,或者对程序进行优化。当我们采用后一种方式时,我们需要了解一个对象是如何占据堆内存空间的,或者说是了解一个对象是由哪些部分组成的。

对象的内存布局

HotSpot虚拟机中,对象在内存中的布局划分为3个区域:对象头(Header),实例数据(Instance Data)以及对齐填充(Padding)。

对象头

HotSpot虚拟机对象的对象头一般包含两部分信息,第一部分用于存储对象自身的运行时数据,例如HashCodeGC分代年龄等信息。在32位和64位的JVM中,这部分数据分别为32bit64bit,官方称这部分数据为Mark Word

另一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。在32JVM中,指针的长度为32bit,在未开启压缩指针的64JVM中,该指针的长度为64bit,如果开启压缩指针,那么为32bit

之前提到对象头一般包含两部分信息,这是因为如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,并且这部分数据也随着JVM位数的不同而不同:32位的JVM上,该区域的长度为32bit,在64位未开启压缩指针的JVM中,这部分数据的长度为64bit,否则为32bit

实例数据

实例数据部分是对象真正存储有效信息的区域,存储了代码中定义的各种字段的内容,包括从父类继承下来的字段和子类中定义的字段。

实例数据紧随对象头,为了提高存储空间的利用率,这部分数据的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配策略如下所示。

doubles & longs

ints & floats

shorts & chars

booleans & bytes

references

可以看出,相同宽度的字段总是被分配到一起,并且在满足这个条件的前提下,在父类中定义的字段会出现在子类字段之前。

对齐填充

对齐填充这部分不是必须存在的,这部分仅仅是起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,因此当对象实例部分数据没有对齐时,就需要对剩余的部分进行填充。

度量工具

JDK 5开始, Java提供了Instrumentation API,通过getObjectSize方法来获取对象的大小,但是getObjectSize方法存在如下两个缺陷,不能准确的计算对象的大小。

不能直接调用getObjectSize方法,而是需要通过-javaagent参数指定一个特定的jar文件(包含Instrumentation代理)来启动Instrumentation的代理程序。

如果一个对象中包含别的对象的引用,那么getObjectSize方法仅仅计算引用的大小,而不包括引用所指向的对象的大小。

由于上述两个缺陷,我们不能直接调用getObjectSize方法来计算对象的大小,但是利用Java的反射机制,我们可以完整的计算一个对象的大小。我们解析对象的每一个Field(使用getDeclaredFields),并遵从如下规则。

Field是基本数据类型时,我们不再计算该Field的大小,因为该Field的大小已经被包含在getObjectSize方法的返回值中。

Field是静态数据或者是常量池中包含的数据,那么我们忽略这些数据,因为这些数据并不是属于对象的。

我们需要保存我们已经计算过的对象的引用,防止重复计算。

如果对象所属的类存在父类,还需要计算父类中成员变量的大小。

jvm-obj-size 是以上思想的具体实现,jvm-obj-size 实现了基本的获取对象本身的大小(sizeOf,仅包含引用本身),以及获取对象真正的大小(fullSizeOf,包含引用所指向的对象)的方法,具体用法以及测试代码详见README文件。

参考

深入理解Java虚拟机

Again about determining size of Java object

Java SE 6 新特性: Instrumentation 新功能

Java对象大小内幕浅析

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66290.html

相关文章

  • 四年来Android面试大纲,作为一个Android程序员

    摘要:再附一部分架构面试视频讲解本文已被开源项目学习笔记总结移动架构视频大厂面试真题项目实战源码收录 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava异常详解Java抽象类和接口的区别Java深拷贝和浅拷...

    不知名网友 评论0 收藏0
  • 读书笔记之深入理解Java虚拟机

    摘要:前言本文内容基本摘抄自深入理解虚拟机,以供复习之用,没有多少参考价值。此区域是唯一一个在虚拟机规范中没有规定任何情况的区域。堆是所有线程共享的内存区域,在虚拟机启动时创建。虚拟机上把方法区称为永久代。 前言 本文内容基本摘抄自《深入理解Java虚拟机》,以供复习之用,没有多少参考价值。想要更详细了解请参考原书。 第二章 1.运行时数据区域 showImg(https://segment...

    jaysun 评论0 收藏0
  • Activity系列博客5篇

    摘要:通过分析源码,不难发现,主要是通过循环解析文件并将信息解析到内存对象,布局文件中定义的一个个组件都被顺序的解析到了内存中并被父子的形式组织起来,这样通过给定的一个就可以将整个布局文件中定义的组件全部解析。 目录介绍 01.前沿介绍 02.handleLaunchActivity 03.performLaunchActivity 04.activity.attach 05.Activi...

    yangrd 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<