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 ...
随机推荐
- Redis 高并发带来的一些问题
前言 本文讲述Redis在遇到高并发时的一些问题.即遇到大量请求时需要思考的点,如缓存穿透 缓存击穿 缓存雪崩 热key处理.一般中小型传统软件企业,很难碰到这个问题.如果有大并发的项目,流量有几百万 ...
- rest framework Views
基于类的意见 Django的基于类的意见是从旧式的观点颇受欢迎. - Reinout面包车里斯 REST框架提供了一个APIView类,它的子类Django的View类. APIView类是从正规不同 ...
- NIO三大组件之Selector选择器
什么是选择器 选择器的作用是完成IO的多路复用.一个通道代表一条连接通路,通过选择器可以同时监控多个通道的IO(输入输出)状况.选择器和通道的关系,是监控和被监控的关系. 使用 重要的成员 Selec ...
- LZZY高级语言程序设计之输入秒数并用时钟的方式表达
import java.util.Scanner;public class MQ5 { public static void main(String[] args) { Scanner sc = ne ...
- 10、字典dict
字典(dict) 字典是有序,键不重复,元素只能是键值对的可变的容器 dict = {'k1':'v1','k2':'v2'} 容器 元素必须键值对 键不重复,重复则会被覆盖 data = {'k1' ...
- gtk+2.0中函数set_widget_font_size()函数在编译时未定义的解决办法
自己写一个头文件即可,代码如下: 在.c文件中包含该头文件即可
- PTA 输出数组元素
7-3 输出数组元素 (15 分) 本题要求编写程序,对顺序读入的n个整数,顺次计算后项减前项之差,并按每行三个元素的格式输出结果. 输入格式: 输入的第一行给出正整数n(1).随后一行给出n个整 ...
- C++11正则表达式
- 经常问到的 BFC 和 IFC 是什么?
什么是BFC?什么作用? Block Formatting Context 块盒子布局发生的区域,浮动元素和其他元素交互的区域 浮动定位和清除浮动的时候只会应用于同一个BFC内的元素.浮动不会影响其他 ...
- [Fundamental of Power Electronics]-PART II-7. 交流等效电路建模-7.3 脉冲宽度调制器建模
7.3 脉冲宽度调制器建模 我们现在已经达成了本章开始的目标,为图7.1推导了一个有效的等效电路模型.但仍存在一个细节,对脉冲宽度调制(PWM)环节进行建模.如图7.1所示的脉冲宽度调制器可以产生一个 ...