这一篇大致说明一下,对象在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. vue集成百度富文本编辑器

    1.前期工作,访问百度富文本官网下载相应的百度富文本文件,根据后端用的技术下载相应的版本,建议下载最新版UTF-8版 (有图有真相,看图) https://ueditor.baidu.com/webs ...

  2. js random获取随机数,获取任意范围内随机整数

     壹 ❀ 引 想着好久没做笔试题了,去GitHub找了面试相关的项目,结果被第一道题难住了.....说难其实也不难,而是我忘记了取范围随机整数怎么写了,不可否认如果当时是我在笔试,肯定也凉了,那么就由 ...

  3. 热度3年猛增20倍,Serverless&云开发的技术架构全解析

    『 作为一个不断发展的新兴技术, Serverless 热度的制高点已然到来.』 或许,Google Trends 所显示的 3 年猛增 20 倍的" Serverless " 搜 ...

  4. 不调用free会内存泄露吗?

    内存泄露的概念大家可以自行百度下,本文不做阐述.本文要讲的是在程序中分配了内存,但是最后没有使用free()函数来释放这块内存,会导致内存泄露吗?比如有如下代码: #include <stdio ...

  5. 神经大条的我-->记录我那些容易忘记的知识点

    1.springmvc中每次进来的request都是保存在ThreadLocal里的,所以不会存在线程问题.可以直接用@Autowired全局注入  参考地址:https://my.oschina.n ...

  6. 微信小程序开发--页面结构

    一.微信小程序开发--页面文件组成 [page.name].js 页面逻辑文件,用于创建页面对象,以及处理页面生命周期控制和数据处理 [page.name].wxml wxml指的是Wei Xin M ...

  7. Elasticsearch 技术分析(九):Elasticsearch的使用和原理总结

    前言 之前已经分享过Elasticsearch的使用和原理的知识,由于近期在公司内部做了一次内部分享,所以本篇主要是基于之前的博文的一个总结,希望通过这篇文章能让读者大致了解Elasticsearch ...

  8. java:选择排序法对数组排序

    最近想练一练Java的算法,然后碰到LeetCode上一道从排序数组删除重复项的小题,刚开始没看到是从排序数组中,就乱写,其实要是排序树组,就比乱序的感觉上好写多了.然后就想回顾下冒泡法对数组排序,凭 ...

  9. Sublime Text 格式化代码

    1.添加快捷键 其实在sublime中已经自建了格式化按钮: Edit -> Line -> Reindent 只是sublime并没有给他赋予快捷键,所以只需加上快捷键即可 Prefer ...

  10. HttpServlet cannot be resolved to a type 解决办法

    刚开始学习Servlet,在Eclipse中新建了一个Servlet,不过页面上报错: Httpservlet cannot be resolved to a type,显然是Eclipse找不到相应 ...