Java虚拟机(二)-对象创建
这一篇大致说明一下,对象在Java堆中对象分配、内存布局以及访问定位
1.对象的创建
虚拟机在遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
类加载时,虚拟机将会给新对象分配内存,对象所需内存的大小在类加载完成后便可完全确定(在内存布局中会说明),为对象分配内存空间等通过与把一块大小确定的内存从java堆中划分出来。虚拟机分配内存有两种方式:指针碰撞和空闲列表
指针碰撞:假设java内存都是规整的,分配完对象的内存区域和空闲区域中间放着一个指针作为分界点的指示器,那所有分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
空闲列表:如果Java内存是不规整的,已使用的内存和空闲内存是相互交错的,那就没有办法使用指针碰撞了,那么虚拟机就必须维护一个列表,记录那些内存块是可用的,在分配的时候从列表汇总找到一块足够大的空间划分给对象实例,并更新列表上的记录。
选择哪种分配方式是有Java堆是否规整来决定的,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理的功能决定。复制算法/标记-整理 是规整的;标记-清理 是不规整的
在并发情况下,虚拟机分配地址可能会出现冲突,解决这个问题有两种方案:一是对分配内存空间的动作进行同步处理-实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(简称TLAB)。哪个线程要分配内存,就在那个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定,虚拟机是否需要使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包含对象头),如果使用TLAB,这个工作可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
然后,设置对象头,如何才能找到类的元数据信息、对象的哈希码、对象GC分代年龄以及偏向锁等信息。
完成上面的工作,从虚拟机的角度看,一个新的对象已经产生了,但从Java程序的角度看,对象创建才刚刚开始-<init>方法还没有执行,所有字段都还为零。执行完new指令后会接着执行<init>方法,把对象按照程序员的医院进行初始化。
2.对象的内存布局
对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;数据长度在32位和64位虚拟机中分别为32bit和64bi他,官方称为“Mark Word”。
在32位虚拟机中,如果对象处于未被锁定的状态下,那么32bit中的25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0.,而在其他状态(具有锁的状态、GC标记、可偏向)下对象的存储内容如下:
| 锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit锁标志位 |
| 无锁状态 | 对象的hashCode | 对象分代年龄 | 0 | 01 |
在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下四种数据
| 锁状态 | 25bit | 4bit | 1bit | 2bit | ||
| 23bit | 2bit | 是否是偏向锁 | 锁标志位 | |||
| 轻量级锁 | 指向栈中锁记录的指针 | 00 | ||||
| 重量级锁 | 指向互斥量(重量级锁)的指针 | 01 | ||||
| GC标记 | 空 | 11 | ||||
| 偏向锁 | 线程ID | Epoch | 对象分代年龄 | 1 | 01 | |
对象头的另一部分是类型指针,即对象指向它的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那么对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Iava对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。
第二部分实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型字段内容,无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。这部分的存储顺序容易受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码总定义顺序的影响。HotSpot虚拟机默认的分配策略参为lllongs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起,在满足这个前提条件下,在父类定义的变量会出现在子类之前。如果CompactFields参数值为true(默认是true),那么子类之中较窄的变量也可能会插入到父类变量的空隙之中。
第三部分对齐填充,就是自动内存管理系统要求对象其实地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8的整数倍,而对象头正好是8字节的倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
3.对象的访问定位
Java对象需要通过栈上的reference数据来操作堆上的具体对象。对象的访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种。
句柄访问:在Java堆上将会划分出一块内存来来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型各自的具体地址信息。

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

优势:
句柄访问:reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
直接指针:速度更快,节省了一次指针定位的时间开销,由于对象的访问Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。Sun HotSpot是使用直接指针访问的,但是从整个软件开发的范围看,各种语言和框架使用句柄来访问的情况也十分常见。
Java虚拟机(二)-对象创建的更多相关文章
- Java 虚拟机的对象创建
堆中存储的内容:在程序运行时,动态创建的对象. 创建对象的四种方式:new,clone(浅复制),反射,反序列化. 浅复制:只能复制当前对象本身,如果当前对象(A)引用了另外的对象(B),则引用对象( ...
- 深入Java虚拟机--判断对象存活状态
程序计数器,虚拟机栈和本地方法栈 首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的.这个三个部分的特点 ...
- 深入理解Java虚拟机(二)——HotSpot对象创建、内存、访问
对象的创建 虚拟机遇到一条字节码new指令时,开始对象创建过程. 首先去检查这个指令的参数是否能在常量池定位到一个类的符号引用: 检查这个符号引用代表的类是否已被加载.解析和初始化,如果没有就必须执行 ...
- JAVA虚拟机:对象的创建过程
简要说明的话,Java对象的创建过程分为下面几步: 1.执行相关检查: 2.为对象分配内存,将分配到的内存空间都初始化为零值: 3.进行构造代码块和构造函数的初始化 下面详细介绍这几个步骤: 1.执行 ...
- JAVA虚拟机:对象的创建
在虚拟机中,当遇到需要new一个对象时,虚拟机首先会去处于方法区的常量池中查找new指令的参数,即查找此类的符号引用是否已存在,并且检查此符号引用的代表类是否已经做过加载.解析和初始化,如果做过则不会 ...
- JAVA虚拟机之对象探秘
上一章主要写到了JVM中运行时数据区域各个部分的功能及其作用.上一章说到了对象是分配在堆上面的,所以接下来我们写到对象在堆内存中是如何创建.如何布局.如何访问.1. 对象的创建 在java程序中对象的 ...
- Java虚拟机判定对象存活算法
1.引用计数算法 描述:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器值为0的对象就是不可能再被使用的. 特点:实现简单,判定效率高. ...
- Java虚拟机二 虚拟机的基本结构
Java虚拟机的基本结构如图所示 类加载子系统负责从文件系统或网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间.除了类的信息外,方法区中可能还会存放运行是的常量池信息, 包括字符串 ...
- java虚拟机(二)--类加载机制和双亲委派模型
一.类的生命周期 加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(Using).卸 ...
随机推荐
- 西门子PLC中各个组织块OB作用(OB1、OB100……)
1.自由循环组织块OB1 S7 CPU启动完成后,操作系统循环执行OB1,OB1执行完成后,操作系统再次启动OB1.在OB1中可以调用FB.SFB.FC.SFC等用户程序使其循环执行.除OB90以外 ...
- Python 爬虫从入门到进阶之路(十八)
在之前的文章我们通过 scrapy 框架 及 scrapy.Spider 类做了一个<糗事百科>的糗百爬虫,本章我们再来看一下相较于 scrapy.Spider 类更为强大的 CrawlS ...
- 原创:Python编写通讯录,支持模糊查询,利用数据库存储
1.要求 数据库存储通讯录,要求按姓名/电话号码查询,查询条件只有一个输入入口,自动识别输入的是姓名还是号码,允许模糊查询. 2.实现功能 可通过输入指令进行操作. (1)首先输入“add”,可以对通 ...
- JAVA面试题 手写ArrayList的实现,在笔试中过关斩将?
面试官Q1:可以手写一个ArrayList的简单实现吗? 我们都知道ArrayList是基于数组实现,如果让你实现JDK源码ArrayList中add().remove().get()方法,你知道如何 ...
- 【DFS练习】Pku1950 Dessert-C++
这道题和这道题很类似. 这里还是说一下坑点,因为前一道题'等式'的加数只有9个,但是这道题目最大到了15,所以在选择不加符号的时候需要判断是用100去乘还是用10去乘就可以了. 基本代码稍微把相关的9 ...
- 【Phabricator】教科书一般的Phabricator安装教程(配合官方文档并带有踩坑解决方案)
随着一声惊雷和滂沱的大雨,我的Phabricator页面终于在我的学生机上跑了起来. 想起在这五个小时内踩过的坑甚如大学隔壁炮王干过的妹子,心里的成就感不禁油然而生. 接下来,我将和大家分享一下本人在 ...
- Ubuntu安装Sublime Text 3
终端命令行安装: Install the GPG key: wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo a ...
- SpringBoot开发案例Nacos配置管理中心
前言 在开发过程中,通常我们会配置一些参数来实现某些功能,比如是否开启某项服务,告警邮件配置等等.一般会通过硬编码.配置文件或者数据库的形式实现. 那么问题来了,如何更加优雅的实现?欢迎来到 Naco ...
- 「PowerBI相关」一款极其优秀的DAX建模工具Tabular Editor
做Excel插件开发的时间久了,大部分人的印象是笔者是做软件开发,谈不上软件,也是插件开发级别. 但笔者更希望大家记住的身份是做BI开发,特别是企业级的BI. 故也借着Excel催化剂这个平台,输送一 ...
- 个人永久性免费-Excel催化剂功能第66波-数据快速录入,预定义引用数据逐字提示
在前面好几波的功能中,为数据录入的规范性做了很大的改进,数据录入乃是数据应用之根,没有完整.干净的数据源,再往下游的所有数据应用场景都是空话.在目前IT化进程推进了20多年的现状,是否还仍有必要在Ex ...