这一篇大致说明一下,对象在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虚拟机(二)-对象创建的更多相关文章

  1. Java 虚拟机的对象创建

    堆中存储的内容:在程序运行时,动态创建的对象. 创建对象的四种方式:new,clone(浅复制),反射,反序列化. 浅复制:只能复制当前对象本身,如果当前对象(A)引用了另外的对象(B),则引用对象( ...

  2. 深入Java虚拟机--判断对象存活状态

    程序计数器,虚拟机栈和本地方法栈 首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的.这个三个部分的特点 ...

  3. 深入理解Java虚拟机(二)——HotSpot对象创建、内存、访问

    对象的创建 虚拟机遇到一条字节码new指令时,开始对象创建过程. 首先去检查这个指令的参数是否能在常量池定位到一个类的符号引用: 检查这个符号引用代表的类是否已被加载.解析和初始化,如果没有就必须执行 ...

  4. JAVA虚拟机:对象的创建过程

    简要说明的话,Java对象的创建过程分为下面几步: 1.执行相关检查: 2.为对象分配内存,将分配到的内存空间都初始化为零值: 3.进行构造代码块和构造函数的初始化 下面详细介绍这几个步骤: 1.执行 ...

  5. JAVA虚拟机:对象的创建

    在虚拟机中,当遇到需要new一个对象时,虚拟机首先会去处于方法区的常量池中查找new指令的参数,即查找此类的符号引用是否已存在,并且检查此符号引用的代表类是否已经做过加载.解析和初始化,如果做过则不会 ...

  6. JAVA虚拟机之对象探秘

    上一章主要写到了JVM中运行时数据区域各个部分的功能及其作用.上一章说到了对象是分配在堆上面的,所以接下来我们写到对象在堆内存中是如何创建.如何布局.如何访问.1. 对象的创建 在java程序中对象的 ...

  7. Java虚拟机判定对象存活算法

    1.引用计数算法 描述:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器值为0的对象就是不可能再被使用的. 特点:实现简单,判定效率高. ...

  8. Java虚拟机二 虚拟机的基本结构

    Java虚拟机的基本结构如图所示 类加载子系统负责从文件系统或网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间.除了类的信息外,方法区中可能还会存放运行是的常量池信息, 包括字符串 ...

  9. java虚拟机(二)--类加载机制和双亲委派模型

    一.类的生命周期 加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(Using).卸 ...

随机推荐

  1. Spring Boot + Elasticsearch 实现索引批量写入

    在使用Eleasticsearch进行索引维护的过程中,如果你的应用场景需要频繁的大批量的索引写入,再使用上篇中提到的维护方法的话显然效率是低下的,此时推荐使用bulkIndex来提升效率.批写入数据 ...

  2. 使用Task实现非阻塞式的I/O操作

    在前面的<基于任务的异步编程模式(TAP)>文章中讲述了.net 4.5框架下的异步操作自我实现方式,实际上,在.net 4.5中部分类已实现了异步封装.如在.net 4.5中,Strea ...

  3. 前端经常碰到的小知识点-----js篇

    一    js 1.可视区宽和高 ① document.documentElement.clientWidth  //可视区的宽度 document.documentElement.clientHei ...

  4. MyBatis从入门到精通:第一章配置MyBatis

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...

  5. Mllib数据类型(密集向量和稀疏向量)

    1.局部向量 Mllib支持2种局部向量类型:密集向量(dense)和稀疏向量(sparse). 密集向量由double类型的数组支持,而稀疏向量则由两个平行数组支持. example: 向量(5.2 ...

  6. css3系列之弹性盒子 flex

    弹性盒子(伸缩盒) 注意,本篇会很长,非常长, 因为弹性盒子的知识点比较多 搜索 弹性盒子的属性  ctrl + F   如果觉得图太小, ctrl + +键 设置弹性盒子的属性: display:f ...

  7. re模块:模式匹配与正则表达式

    一.用正则表达式查找文本模式 正则表达式,简称regex,是文本模式的描述方法.比如\d是一个正则表达式,用于表示一位0~9的数字.在一个模式后面加上花括号包围的数字n(如{n}),表示匹配这个模式n ...

  8. Lucene01--倒排索引思想

    Lucene01--倒排索引思想 1. 倒排索引的概念: 首先对数据按列拆分存储,然后对文档中的数据分词,对词条进行索引,并记录词条在文档中出现的位置.这样查找时只要找到了词条,就找到了对应的文档.概 ...

  9. 15号作品teamfinal使用体验

    通过使用这款软件,可以轻松的查阅所处学期的任意周中某一天中的基教.一教.二教.三教和土木楼中的空教室,方便了同学去寻找空教室的方便,方便同学们上自习,节省寻找教室的时间,提供了非常大的便利. 打开界面 ...

  10. 万能RecyclerView的数据适配器BaseRecyclerViewAdapterHelper

    今天楼主才发现github上有这么一个好用的开源代码,充满好奇心的楼主马上使用了,特地分享给大家. 此项目的github地址: https://github.com/CymChad/BaseRecyc ...