JVM探秘:Java对象
本系列笔记主要基于《深入理解Java虚拟机:JVM高级特性与最佳实践 第2版》,是这本书的读书笔记。
对象的创建
虚拟机遇到一条 new 指令时,首先去检查这个指令的参数是否能在方法区常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。
类加载检查过后,虚拟机为新生对象分配内存。对象所需内存大小在类加载过后就完全确定,为对象分配空间就等同于,从 Java 堆中划分出一块确定大小的内存。
此时有两种情况:
如果 Java 堆中内存是规整的,那么虚拟机将会采用指针碰撞(Bump the Pointer)分配内存。也就是用过的内存放在一边,空闲的内存放另一边,中间有个指针作为分界点指示器,那么分配内存就是把指针向空闲那一边挪动了与对象相同大小的距离。
如果 Java 堆中内存不规整,已使用和未使用的内存相互交错,那么虚拟机将会采用空闲列表(Free List)分配内存。也就是虚拟机会维护一个空闲列表,记录哪些内存块可用,分配内存时就从空闲列表中找到一块足够大的空间分给对象实例,并更新列表。
选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整由垃圾收集器是否带有压缩整理功能决定。比如Serial、ParNew这种基于复制算法和标记-整理算法的收集器,就是采用指针碰撞法分配内存。而CMS这种基于标记-清除算法的收集器就是采用空闲列表。
关于垃圾收集算法和垃圾收集器,后面会单独有文章讲到。
还有一个问题就是要保证分配内存时的线程安全,即使是简单的指针碰撞修改一下指针的位置,在并发情况下也不是线程安全的。解决这个问题有两种方案,一种是对分配内存的动作进行同步处理,保证操作的原子性;另一种就是前面提到的本地线程分配缓冲TLAB。
在 Java 堆中内存分配完成后,虚拟机会将已分配的内存空间初始化为零值(不包括对象头)。这保证了对象的实例字段在 Java 代码中可以不用赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。如果采用了 TLAB,这一步也可以提前至 TLAB 分配时进行。
接下来对对象进行必要的设置,例如这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息都存放在对象的对象头(Object Header)中。
这个时候,从虚拟机的角度看一个新的对象已经产生了,但从 Java 程序角度看对象的创建才刚开始,init 方法还没执行,所有的字段都还为零。所以这时会执行 init 的方法,把对象按照程序员的意愿初始化。
对象的内存布局
在 HotSpot 虚拟机中,对象在内存中的布局分为三块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称这部分数据为 Mark Word。另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针确定对象是哪个类的实例。但不是所有的虚拟机实现都会在对象数据上保留类型指针,也就是说查找对象的元数据信息不一定要经过对象本身,这点会在下面对象定位方式中讲到。另外如果对象是一个 Java 数组,对象头中还会记录数组长度,用来确定 Java 对象的大小。
HotSpot虚拟机对象头Mark Word:
| 存储内容 | 标志位 | 状态 |
|---|---|---|
| 对象哈希码、分代年龄 | 01 | 未锁定 |
| 指向锁记录的指针 | 00 | 轻量级锁定 |
| 指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
| 空,不需要记录信息 | 11 | GC标记 |
| 偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 |
实例数据是对象的真正有效数据,也就是代码中定义的字段内容。这部分存储数据受到虚拟机分配策略参数和字段在 Java 源码中定义顺序的影响。HotSpot 虚拟机默认分配策略为 longs/doubles、ints、shorts/chars、bytes/booleans、oops。相同宽度的字段总是被分配到一起。在这个前提下,父类中定义的变量会出现在子类之前。如果CompactFields参数为true,那么子类中较窄的变量也可能插到父类变量的空隙中。
对齐填充并不是必然存在的,也没有特殊含义,它仅仅是占位符。由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是对象大小必须是8字节的整数倍。因此当对象实例数据没有对齐时,就会通过对齐填充来补全。
对象的访问定位
建立对象是为了使用对象,Java 程序需要通过 Java 栈上的 reference 数据来操作 Java 堆上的对象。reference 类型就是一个指向对象的引用。对象的访问方式取决于虚拟机实现,目前的访问方式主要有使用句柄和直接指针两种。
如果使用句柄访问,Java 堆中将会划分出一块内存作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据的地址,如下图:

如果使用直接指针访问,reference 中存储的直接就是对象地址,但是对象中还会放置访问类型数据的指针,如下图:

这两种对象访问方式各有优劣,使用句柄访问的话,reference 中存储的是稳定的句柄地址,垃圾收集时移动对象的话,只会修改句柄中的实例数据指针,而 reference 本身不需要修改。
使用直接指针访问的最大好处就是速度更快,节省了一次指针定位的时间,对象访问非常频繁,这种节省极少成多的话也是非常可观。
HotSpot 虚拟机使用的是直接指针访问的方式。
JVM探秘:Java对象的更多相关文章
- JVM总结-java对象的内存布局
在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我们还可以通过反射机制.Object.clone 方法.反序列化以及 Unsafe.allocateInstance ...
- 【JVM】java对象
一.对象内存布局 对象在内存中存储可分为3块区域:对象头,实例数据,对齐填充 1.对象头 对象头包含两部分内容. 第一部分:存储对象自身的运行时数据,哈希吗(hashCode),GC分代年龄,锁状态标 ...
- Java对象在JVM中的生命周期
当你通过new语句创建一个java对象时,JVM就会为这个对象分配一块内存空间,只要这个对象被引用变量引用了,那么这个对象就会一直驻留在内存中,否则,它就会结束生命周期,JVM会在合适的时 ...
- Java对象的创建 —— new之后JVM都做了什么?
Java对象创建过程 1. 类加载检查 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载.解析和初始化过.如果没 ...
- 【深入理解JVM】:Java对象的创建、内存布局、访问定位
对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含的主要过程包括了类加载检查.对象分配内存.并发处理.内存空间初始化.对象设置.执行ini方法等. 主要流 ...
- JVM —— Java 对象占用空间大小计算
零. 为什么要知道 Java 对象占用空间大小 缓存的实现: 在设计 JVM 内缓存时(不是借助 Memcached. Redis 等), 须要知道缓存的对象是否会超过 JVM 最大堆限制, 假设会超 ...
- JVM(2)--深入理解java对象创建始终
java对象探秘 java是一门面向对象的语言,我们无时无刻不在创建对象和使用对象,那么java虚拟机是如何创建对象的?又是如何访问对象的?java对象中究竟存储了什么运行时所必需的数据?在学习了ja ...
- 99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?
本文转载自公众号:石彬的架构笔记,阅读大约需要8分钟. 作者:李瑞杰 目前就职于阿里巴巴,资深 JVM 研究人员 在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我 ...
- JVM源码分析之Java对象头实现
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Java对象头实现 HotSpot虚拟机中,对象在内存中的布局分为三 ...
随机推荐
- list extend 和 append
append 一次追加一个列表 extend 一次追加所有的元素 单个的形式加入
- C语言中内存的管理
一.动态内存分配与释放 1.为什么要使用动态内存分配,以下看一个实例,关于超市中购买记录的一段程序 #include <stdio.h> #include <string.h> ...
- html实体字符转换成字符串
function EntityToString(value) { let tag = document.createElement("div"); tag.innerHTML = ...
- 2019徐州网络赛 I.query
这题挺有意思哈!!!看别人写的博客,感觉瞬间就懂了. 这道题大概题意就是,给一串序列,我们要查找到l-r区间内,满足min(a[ i ],a[ j ]) = gcd(a[ i ],a[ j ]) 其实 ...
- DTCC 2019 | 深度解码阿里数据库实现 数据库内核——基于HLC的分布式事务实现深度剖析
摘要:分布式事务是分布式数据库最难攻克的技术之一,分布式事务为分布式数据库提供一致性数据访问的支持,保证全局读写原子性和隔离性,提供一体化分布式数据库的用户体验.本文主要分享分布式数据库中的时钟解决方 ...
- CSDN-Java培训 - 看看这次会有多少人跟风...
2019年5月8日,闲来无事(最近答辩还没事......),存个档. 看看这一波风口,记录互联网+教育.
- 关于DOM的一些基础问题
什么是 DOM? DOM 是一项 W3C (World Wide Web Consortium) 标准,全称是文档对象模型(Document Object Model). DOM 定义了访问文档的标准 ...
- 条件随机场(CRF) - 3 - 概率计算问题
声明: 1,本篇为个人对<2012.李航.统计学习方法.pdf>的学习总结,不得用作商用,欢迎转载,但请注明出处(即:本帖地址). 2,由于本人在学习初始时有很多数学知识都已忘记,所以为了 ...
- Python--day25--抽象类
什么是抽象类: 抽象类: #一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' ...
- C# 使用转换语义版本号
本文告诉大家如何转换语义版本号,那么什么是语义版本号,语义版本号(semantic version)就是版本号带 alpha 等的版本号 在以前的版本号都是这样 1.2.1 的格式,这个格式可以使用微 ...