• HotSpot虚拟机中的对象


    2.2 HotSpot虚拟机中的对象

    2.2.1 对象的创建

    注意:这里所指的对象限于普通Java对象,不包括数组和Class对象等

    当Java虚拟机遇到一条new的字节码指令时,会触发对象创建。

    总结1 HotSpot中的对象创建过程:

    (1)首先将去检查这个指令的参数是否能在常量池(Java方法区中存储着类型信息)中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。类加载主要是为了确定对象的基本信息,例如大小等。

    (2)在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定。

    (3)内存分配完成之后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值。

    (4)Java虚拟机还要对对象进行必要的设置(主要是对象头进行设置),例如这个对象是哪个类的实例如何才能找到类的元数据信息、对象的哈希码(实际上对象的哈希码会延后到真正调用Object::hashCode()方法时才计算)、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。

    总结2 Java虚拟机对对象进行内存分配的方式:

    (1)基于“指针碰撞”的分配方法:

    内存区域被划分为两部分,占用部分和空闲部分,中间一个指针作为分界线,如果新对象要内存了,指针向空闲区域移动与对象大小相等的区域出来,放入对象。

    (2)基于“空闲列表”的分配方法:

    如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

    选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。

    总结3 解决并发情况下,对象内存分配不安全的问题:

    (1)一种是对分配内存空间的动作进行同步处理——实际上虚拟机是采用CAS配上失败重试的方式保证更新操作的原子性;(查看CPP源码可以知道HotSpot采用的就是这种方式)

    (2)另外一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local AllocationBuffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。

    通俗易懂的讲就是:(1)所有线程的对象一起抢内存,先抢先得。(2)不同线程的对象们都预先都有点内存,先自己用着,不够了,再一起抢。

    经过总结1中所经过的所有过程之后,从虚拟机的视角来看,一个新的对象已经产生了。但是从Java程序的视角看来,对象创建才刚刚开始——构造函数,即Class文件中的<init>()方法还没有执行,所有的字段都为默认的零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。

    一般来说(由字节码流中new指令后面是否跟随invokespecial指令所决定,Java编译器会在遇到new关键字的地方同时生成这两条字节码指令,但如果直接通过其他方式产生的则不一定如此),new指令之后会接着执行<init>()方法

    2.2.2 对象的内存布局

    1、对象在堆内存中的布局可以划分为三个部分:(1)对象头(2)实例数据(3)对齐填充

    2、对象头包含两类信息:(1)自身运行时的信息(哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳)(2)类型指针,对象指向它的类型元数据的指针(方法区中的类型信息)

    特别注意,当对象是一个数组是,还需要存储数组的长度信息。

    3、实例数据就是对象的数据,没啥好说的

    4、对齐填充的存在是由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者
    2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

    2.3.3 对象的访问定位

    1、我们创建好对象之后,Java方法区中的局部变量也存在了该对象的reference类型,我们可以通过引用找到对象,但是这个引用是如何定位对象的,有不同的方式。

    2、主流的定位方式有两种(下面谈到的对象类型数据,可以看成其他的对象引用):

    (1)句柄访问Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息

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

    3、两种访问定位方式各自的优势:

    (1)句柄访问:句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。

    (2)直接访问:用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本。

    注:HotSpot虚拟机采用第二种访问方式。

  • 相关阅读:
    ASP.NET Core WebAPI学习-4
    PIESDKDoNet二次开发配置注意事项
    PIE SDK影像快速拼接
    PIE SDK加载WMS服务数据
    PIE SDK加载自定义服务数据
    PIE SDK 距离分类和最大似然分类
    PIE SDK矢量点生成等值线、面
    PIE SDK与OpenCV结合说明文档
    C#录制声卡声音喇叭声音音箱声音
    C#录制屏幕采集系统桌面画面
  • 原文地址:https://www.cnblogs.com/doubest/p/12858329.html
Copyright © 2020-2023  润新知