本文摘自深入理解 Java 虚拟机第三版

对象的创建

Java 是一门面向对象的语言,Java 程序运行过程中无时无刻都有对象被创建出来。从语言层面看,创建对象只是一个 new 关键字而已,而在虚拟机中,对象(仅限于普通 Java 对象,不包括数组和 Class 对象等)的创建又是怎么一个过程呢?

以 Hotspot 虚拟机为例,当虚拟机遇到一条字节码指令,首先会检查这个指令的参数是否能在常量池中定位到一个符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化,如果没有,那么会先执行对应的类加载过程

类加载检查通过后,虚拟机将为新生对象分配内存。对象所需内存的大小在类加载过程中即可完全确定,为对象分配空间的任务实际上等同于把一块确定大小的内存区域从 Java 堆中划分出来,并分配给对象。划分内存的方式主要有两种:

  • 指针碰撞法

    即假设 Java 堆中内存是绝对规整的,已被使用的内存放在一边,空闲的内存放在另一边,中间有一个指针作为分界点的指示器,那么当需要分配内存时,只需要将指针向空闲空间的方向挪动一段与对象大小相等的距离即可

  • 空闲列表法

    这个情况就是 Java 堆中内存并不是规整的,已使用内存和空闲内存相互交错在一起。此时虚拟机必须维护一个列表,记录哪些内存块可用,分配时从列表中找出一块足够大的空间划分给对象实例,并更新列表记录

由此可见,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理的能力决定

除了如何划分可用空间以外,还有一个需要考虑的问题:对象创建在虚拟机中是非常频繁的行为,即使仅仅只是修改指针的位置,在并发情况下也并不是安全的,有可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用原来的指针来分配内存。解决该问题的方法有两个:一个是对分配内存空间的动作片进行同步处理,实际上虚拟机是采用 CAS 配上失败重试机制来保证更新操作的原子性的;另一种是把内存分配的动作按照线程划分到不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer),简称 TLAB,哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓冲区时才需要同步锁定。虚拟机是否使用 TLAB,可以通过 -XX:+/UseTLAB 参数来设定

内存分配完成后,虚拟机必须先将分配到的内存空间(但不包括对象头)都初始化为零值,如果使用了 TLAB 的话,这项工作也可以提前在 TLAB 分配时顺便进行。这步操作保证了对象的实例字段在 Java 代码中可以不赋初值就直接使用,使程序可以访问到这些字段的数据类型对应的零值

接下来,Java 虚拟机还需要对对象的对象头(Object Header)进行设置,对象头存放一些信息如这个对象是哪个对象的实例、如何找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等

上面的工作完成之后,从虚拟机的视角来看,一个新的对象已经诞生了,而从 Java 程序的角度来看,对象的创建才刚刚开始,对象的构造函数,即 Class 文件的 <init> 方法尚未执行,所有的字段都是默认的零值。new 指令之后会接着执行 <init> 方法,对对象进行初始化,这样一个真正可用的对象才算完全构造出来

对象的内存布局

在 HotSpot 虚拟机中,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

1. 对象头

Hotspot 虚拟机对象的对象头部分包括两类信息,第一类是用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有锁、偏向线程 ID、编向时间戳等。这部分数据的长度在 32 位和 64 位虚拟机中分别为 32 个比特和 64 个比特,官方称之为 “Mark Word”

但实际上,对象需要存储待运行时数据很多,已经超过了 32、64 位 Bitmap 结构所能记录的最大限度,因此 Mark Word 被设计成一个有着动态定义的数据结构,以便尽可能多的复用自己的存储空间,例如在 32 位 JVM 中是这样存储的:

对象头的另一个部分是类型指针,即对象指向它的类型元数据的指针,Java 虚拟机通过这个指针来确定这个对象是属于哪个类的实例。然而,并不是所有的虚拟机都会在对象头上保留类型指针,换句话说,查找对象的元数据信息不一定要经过对象本身。此外,如果对象是个数组,那么在对象头中还必须有一块用于记录数组长度的数据

2. 实例数据

实例数据部分是对象真正存储的有效信息,即我们在代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来

3. 对齐填充

对齐填充并不是必然存在的,它仅仅起着占位符的作用,因为 Hotspot 虚拟机的自动内存管理机制要求对象的大小必须是 8 字节的整数倍。因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全

对象的访问定位

Java 程序会通过栈上的 reference 数据来操作堆上的具体对象。在 Java 虚拟机规范中,只是规定 reference 类型是一个指向对象的引用,并没有定义要用什么方式实现。因此对象访问方式是由虚拟机自主实现的,主流的访问方式主要有使用句柄和直接指针两种:

  • 句柄访问

    Java 堆中会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄包含了对象实例数据和类型数据各自具体的地址信息

  • 直接指针

    reference 中存储的就是对象地址,所以如果只是访问对象本身的话,可以避免多次开销。另外,如果使用直接指针访问,Java 堆中对象的内存布局就必须考虑如何放置用于访问类型数据的相关信息

两种方式各有千秋,使用句柄的好处就是 reference 中存储的是稳定的句柄地址,如果对象被移动(例如进行垃圾收集),那么只会改变句柄中实例数据指针,而 reference 本身不需要修改。而使用直接指针的好处就是速度更快,节省时间开销。在 HotSpot 虚拟机中主要使用直接指针方式进行对象访问

JVM 堆中对象分配、布局和访问的更多相关文章

  1. JVM虚拟机-了解Java堆中对象分配、布局和访问的全过程

    目录 前言 对象的创建 类加载检查 分配内存 内存空间分配方式 指针碰撞 空闲列表 并发时的内存分配 同步处理:CAS 本地线程分配缓冲:TLAB 初始化零值 设置对象头 执行 init 方法 对象的 ...

  2. JVM高级特性-二、JVM在堆中对象的分配、布局、访问过程

    前面介绍了jvm运行时数据区域后,下面讲解下对内存中数据的其他细节,看他们是如何创建.布局及访问的 一.对象的创建 1.对象的分配 对象的创建分配方式主要有两种:指针碰撞和空闲列表 指针碰撞: 假设堆 ...

  3. java对象在JVM堆中的数据结构

    java对象和数组是存放在堆中的,那么这些instance的数据结构是什么呢? 对象头:对象头存放的是这个对象的一些元数据信息.例如每个对象都有哈希值,GC分代年龄,锁状态标志等,这些信息就是存放在对 ...

  4. jvm对大对象分配内存的特殊处理(转)

    前段日子在和leader交流技术的时候,偶然听到jvm在分配内存空间给大对象时,如果young区空间不足会直接在old区切一块过去.对于这个结论很好奇,也比较怀疑,所以就上网搜了下,发现还真有这么回事 ...

  5. 「每日五分钟,玩转JVM」:对象内存布局

    概览 一个对象根据不同情况可以被划分成两种情况,当对象是一个非数组对象的时候,对象头,实例数据,对齐填充在内存中三分天下,而数组对象中在对象头中多了一个用于描述数组对象长度的部分 对象头 对象头分为两 ...

  6. 99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?

    本文转载自公众号:石彬的架构笔记,阅读大约需要8分钟. 作者:李瑞杰 目前就职于阿里巴巴,资深 JVM 研究人员 在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我 ...

  7. 重磅硬核 | 一文聊透对象在 JVM 中的内存布局,以及内存对齐和压缩指针的原理及应用

    欢迎关注公众号:bin的技术小屋 大家好,我是bin,又到了每周我们见面的时刻了,我的公众号在1月10号那天发布了第一篇文章<从内核角度看IO模型的演变>,在这篇文章中我们通过图解的方式以 ...

  8. JVM基础:深入学习JVM堆与JVM栈

    转自:http://developer.51cto.com/art/201009/227812.htm JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题, ...

  9. jvm学习记录-对象的创建、对象的内存布局、对象的访问定位

    简述 今天继续写<深入理解java虚拟机>的对象创建的理解.这次和上次隔的时间有些长,是因为有些东西确实不好理解,就查阅各种资料,然后弄明白了才来做记录. (此文中所阐述的内容都是以Hot ...

随机推荐

  1. Windows10系统下Hadoop和Hive开发环境搭建填坑指南

    前提 笔者目前需要搭建数据平台,发现了Windows系统下,Hadoop和Hive等组件的安装和运行存在大量的坑,而本着有坑必填的目标,笔者还是花了几个晚上的下班时候在多个互联网参考资料的帮助下完成了 ...

  2. python开发基础(二)运算符以及数据类型之tuple(元组)

    # encoding: utf-8 # module builtins # from (built-in) # by generator 1.147 """ Built- ...

  3. Java_大体介绍(超级短的那种)

    Java三大版本 Java SE: Java Standard Edition, 定位于客户端, 用于桌面应用软件编程 Java ME: Java Micro Edition, 用于嵌入式系统开发 J ...

  4. go beego框架 入门使用 (一)

    ---恢复内容开始--- 谢谢您花时间读我写的随笔,有问题的话欢迎留言,看到的话都会回复的! beego框架 分为Web版,Api版     api版目录      web版目录      (区别 : ...

  5. yaf拓展安装步骤

    1.wget http://pecl.php.net/get/yaf-3.0.7.tgz //下载yaf.tar 2.tar zxvf yaf-3.0.7.tgz //解压 3.cd yaf-3.0. ...

  6. Go读取论文并转换为simhahs

    package main import ( "fmt" _"flag" _ "os" _ "io/ioutil" _&q ...

  7. C#循环判断密码

      要求用户输入用户名和密码,只要不是admin.888888就一直提示用户名或密码错误,请重新输入 while (true) { Console.WriteLine("请输入帐号和密码&q ...

  8. 纯CSS实现3D正方体动画效果

    前言 纯CSS实现3D正方体动画效果,此方法是通过transform的旋转(rotate)和位移(translate)实现的,具体效果是鼠标滑过时正方体的一个面会产生位移 效果图 ​

  9. Handler的源码和常见问题的解答不崩溃

    Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间,如果你想在Android中实 ...

  10. ImpalaTest

    package com.niewj.demo; import java.sql.Connection; import java.sql.DriverManager; import java.sql.R ...