JVM虚拟机-了解Java堆中对象分配、布局和访问的全过程
前言
对象是Java中最常被提到的概念,也是Java的灵魂,Java中一切皆是对象。
JVM是Java的根基,理解灵魂与根基是如何融合的,对于理解Java本身至关重要。
对象的创建
对象的创建从Java语言层面上,往往就是一个简单的new即可搞定,而深入JVM的底层,可就复杂的多了。
JVM中对象的创建过程,分为如下5步,如图:

类加载检查
虚拟机遇到一条 new 指令后,会执行如下几个步骤:
- 首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用。
- 检查这个符号引用代表的类是否已被加载过、解析过、初始化过。
- 如果没有,那必须先执行相应的类加载过程。
- 确定对象所需的内存大小。
- 类加载检查通过后,进入下一步骤,为新生对象分配内存。
分配内存
为对象分配空间相当于分田地,就是将空余的内存划分一块给对象。
由于不同的JVM虚拟机,Java堆的内存规整情况是不同的,所以分配方式上也会有一定区别。
Java 堆是否规整,由JVM所采用的垃圾收集器是否带有压缩整理功能决定的
内存空间分配方式
指针碰撞
Java堆中内存是规整(无内存碎片)时使用,即垃圾收集器有压缩整理功能时。
原理就是将用过的内存放在一块,没用的放一块,中间用指针做标识,分配的时候将指针向空闲内存区域移动。
空闲列表
Java堆中内存不规整,即垃圾收集器无压缩整理功能时。
使用内存和空闲内存相互交错,JVM需维护一个列表,记录上哪些内存是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
两者具像化呈现如图:

并发时的内存分配
创建对象是很频繁的事,并发创建对象的时候有一个很重要的问题,就是线程安全。
如:程序中创建对象A和对象B,底层VM给A对象分配内存,指针没来及修改,对象B同时使用原来的指针分配内存。
JVM一般采用一下两种方式来保障线程安全。
同步处理:CAS
CAS 是乐观锁的一种实现方式。
所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
本地线程分配缓冲:TLAB
为每一个线程预先在 Eden 区分配一块儿内存,即为TLAB
JVM 在给线程中的对象分配内存时,首先在 TLAB 分配
当对象大于 TLAB 中的剩余内存不足或已用尽时,再采用上述的 CAS 进行内存分配
更多关于CAS的内容可以参考博主的另一篇文章Java并发/多线程-CAS原理分析
初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)
这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
设置对象头
对象头是虚拟机对对象进行必要的设置
对象头存放了关于对象的各种信息如:
- 对象是哪个类的实例
- 如何才能找到类的元数据信息
- 对象的哈希码
- 对象的 GC 分代年龄
- ……
根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生
从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。
所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
对象的内存布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域。
对象头
对象头上面已经介绍过了,其实主要就是分为两类:
- 用于存储对象自身的运行时数据
- 类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例
实例数据
这部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对齐填充
对齐填充不是必然存在的,没有实际意义,就是用来补位的。
因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,对象的大小必须是 8 字节的整数倍。
所以,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
对象的访问定位
建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。
对象的访问方式由虚拟机实现而定,目前主流的访问方式有如下两种方式
使用句柄
Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,如图:

直接指针
如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。

二者对比
使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。
使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。
参考:《深入理解JVM虚拟机》第三版(吹爆)
JVM虚拟机-了解Java堆中对象分配、布局和访问的全过程的更多相关文章
- JVM 堆中对象分配、布局和访问
本文摘自深入理解 Java 虚拟机第三版 对象的创建 Java 是一门面向对象的语言,Java 程序运行过程中无时无刻都有对象被创建出来.从语言层面看,创建对象只是一个 new 关键字而已,而在虚拟机 ...
- JVM高级特性-二、JVM在堆中对象的分配、布局、访问过程
前面介绍了jvm运行时数据区域后,下面讲解下对内存中数据的其他细节,看他们是如何创建.布局及访问的 一.对象的创建 1.对象的分配 对象的创建分配方式主要有两种:指针碰撞和空闲列表 指针碰撞: 假设堆 ...
- 面试题:JVM在Java堆中对对象的创建、内存结构、访问方式
一.对象创建过程 1.检查类是否已被加载 JVM遇到new指令时,首先会去检查这个指令参数能否在常量池中定位到这个类的符号引用,检查这个符号引用代表的类是否已被加载.解析.初始化,若没有,则进行类加载 ...
- jvm详情——1、堆中存什么?栈中存什么?
数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身, ...
- 翻译:JVM虚拟机规范1.7中的运行时常量池部分(二)
本篇为JVM虚拟机规范1.7中的运行时常量池部分系列的第二篇. 4.4.4. The CONSTANT_Integer_info and CONSTANT_Float_info Structures ...
- JVM学习--(八)java堆分析
上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...
- Java虚拟机三 Java堆和栈
Java堆是和Java应用程序关系最为紧密的内存空间,几乎所有的对象都存放在堆中.并且堆是完全自动化管理的. 根据垃圾回收机制的不同,Java堆有可能有不同的结构.最为常见的一种就是将整个Java堆分 ...
- 翻译:JVM虚拟机规范1.7中的运行时常量池部分(一)
原文链接: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4 Java Virtual Machine i ...
- JVM虚拟机21: 1.8中废弃永久代(PermGen)迎来元空间(Metaspace)
1.JDK8永久代的废弃 JDK8 永久代变化如下图: 1.新生代:Eden+From Survivor+To Survivor 2.老年代:OldGen 3.永久代(方法区的实现) : PermGe ...
随机推荐
- Flutter Web 支持现已进入稳定版
作者 / Mariam Hasnany, Product Manager, Flutter 我们对 Flutter 的愿景是成为一个可移植的 UI 框架,在全平台上构建精美的应用体验.做为 Flutt ...
- Python开发环境从零搭建-02-代码编辑器Sublime
想要从零开始搭建一个Python的开发环境说容易也容易 说难也能难倒一片开发人员,在接下来的一系列视频中,会详细的讲解如何一步步搭建python的开发环境 本文章是搭建环境的第2篇 讲解的内容是:安装 ...
- apk动态调试
android.os.Debug类提供了isDebuggerConnected()用于检测是否有调试器链接: AndroidManifest的application节点中加入android:debug ...
- 攻防世界 reverse reverse-for-the-holy-grail-350
reverse-for-the-holy-grail-350 tu-ctf-2016 程序流程很简单,就一个检验函数: 1 __int64 __fastcall stringMod(__int64 ...
- JAVA面试核心知识点(一):计算机网络
一·计算机网络 1.1 网络基础知识 OSI 七层协议(制定标准使用的标准概念框架): 物理层(传递比特流0101)->数据链路层(将比特流转换为逻辑传输线路)->网络层(逻辑编址,分组传 ...
- Django使用数据库(配置数据库,基本的增删改查a)
第一步在setting文件中配置DATABASES设置 然后更改__init__文件 打开APP中models文件,导入并创建数据库 最后打开终端执行以下命令 python manage.py mak ...
- 「HTML+CSS」--自定义按钮样式【003】
前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...
- 面向前端开发人员的VSCode自动化插件
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://blog.bitsrc.io/vscode-automations-for-fro ...
- [Kong 与 Konga 与 Postgres数据库] 之 Kuberneres 部署
1.Kong的概述 Kong是一个clould-native.快速的.可扩展的.分布式的微服务抽象层(也称为API网关.API中间件或在某些情况下称为服务网格)框架.Kong作为开源项目在2015年推 ...
- MongoDB教程--配置与入门
MongoDB简介 阿里云配置MongoDB 数据库的增删查改 MongoDB 数据最重要的操作是Key-Value的映射.有了这样的映射,可以直接通过关键字去寻找想要的值.例如,通过用户的ID寻找与 ...