这一篇大致说明一下,对象在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. windows美化工具7+ Taskbar Tweaker

    今天分享一个windows美化工具 7+ Taskbar Tweaker 调整工具专为 Windows 任务栏工作者量身定制,支持 Windows 7 以及更高版本的(非服务器版)微软操作系统平台. ...

  2. 后端访问sso后,如何返回前端vue页面(后端redirect跳转,vue代理,axios带参)

    由于项目要加上公司的sso,出现的一系列问题,找到解决办法,在此记录一下.可能并不适合其他项目,给个参考. 前提: 前端是vue.js,后端springboot sso配置需要增加公司自己的maven ...

  3. 基于SpringBoot-Dubbo的微服务快速开发框架

    简介: 基于Dubbo的分布式/微服务基础框架,为前端提供脚手架开发服务,结合前一篇--Web AP快速开发基础框架,可快速上手基于Dubbo的分布式服务开发,项目代码: https://github ...

  4. 20141126-传智播客.NET3.2版

  5. 【HDU - 3533】Escape(bfs)

    Escape  Descriptions: 一个人从(0,0)跑到(n,m),只有k点能量,一秒消耗一点,在图中有k个炮塔,给出炮塔的射击方向c,射击间隔t,子弹速度v,坐标x,y问这个人能不能安全到 ...

  6. Excel催化剂开源第27波-Excel离线生成词云图

    在数据分析领域,词云图已经成为在文本分析中装逼的首选图表,大家热烈地讨论如何在Python上做数据分析.做词云图. 数据分析从来都是Excel的主战场,能够让普通用户使用上的技术才是最有价值的技术,一 ...

  7. Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术

    Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术 Excel催化剂   2019.01.12 14:10* 字数 2948 阅读 41评论 0喜欢 0 编辑文章 在Excel催化剂的几大 ...

  8. 为什么选择 Spring 作为 Java 框架

    1. 概述 在本文中,我们将讨论 Spring 作为最流行的 Java 框架之一的主要价值体现. 最重要的是,我们将尝试理解 Spring 成为我们选择框架的原因.Spring 的详细信息及其组成部分 ...

  9. [剑指offer] 23. 二叉搜索树的后序遍历序列

    题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 思路: 解法一:递归 二叉搜索树,后序遍历的数组中 ...

  10. Docker 工作原理及容器化简易指南

    Docker 非常棒! 它使软件开发者无需担心配置和依赖性,在任何地方打包,发送和运行他们的应用程序.而在与 Kubernetes 相结合后,它使应用集群部署和管理变得更方便.这使得 Docker 深 ...