JVM:HotSpot 对象的创建、内存、访问

本文详细探讨了Java中对象的创建过程,包括JVM如何根据new指令加载类和分配内存,以及并发情况下如何解决数据安全问题。对象内存布局部分介绍了对象头、指针、实例数据和对齐补充等组成部分,并通过JOL工具展示了对象占用的字节数。最后,讨论了对象访问定位的两种方式:句柄访问和直接指针引用,以及它们的优缺点。
摘要由CSDN通过智能技术生成

1、对象的创建

在我们的JAVA语言中,当我们需要创建一个对象时,只需要使用一个类去new出一个实例,这样就创建好了一个类,但是接下来通过虚拟机层面来观察这个实例出现的过程:


  • 在我们的JAVA层面上使用一个new关键字并启动程序时,JAVA虚拟机会遇到一条new的字节码指令,JVM会先去检查这个指令的参数是否在常量能拿到这个类的符号引用,然后根据符号引用去判断这个类是否在加载状态,如果没有,那么则根据上述类的加载过程进行加载。

  • 在加载这个类的时候,JVM会给这个对象分配一块内存空间,内存空间分配的方式有两种:

    1. 假设内存空间规整,那么JVM就会划分两块空间,一块空闲空间,一块非空闲空间,两块空间之间存在一个分界点的指针,用来划分两个空间的大小,如果分配了一块空间给对象,那么指针就会以这个空间的大小向空闲空间挪动,这就是第一种方法 "指针碰撞 (Bump The Pointer)"。

    2. 假设内存空间不规整,则JAVA虚拟机会维护一个队列,这个队列是空闲空间的队列,当有一个对象需要分配内存时,就会划分足够大小的队列节点给该对象使用,这就是第二种方法 "空闲队列"。

  • 不过这里需要我们注意的是:在并发频繁创建对象的情况下其实也会导致数据安全的问题,假设对象A在需要分配空间的时候,指针就将会给他分配,这个时候对象B就要被原来的指针划分空间,JVM里也有自己的处理方式:

    1. 使用CAS的方式进行自旋尝试,保证操作的原子性。

    2. 本地线程缓冲,每个线程都有自己的一块内存空间,在自己的内存空间修改。

  • 当内存分配完毕之后,JVM就会初始化该内存空间,所以此时时候这个实例对象的属性都是零值,接下来,虚拟机会对该对象进行一系列的处理,例如,这个实例的具体是什么,如果才能找到这个对象的元数据,对象头等等。

  • 此时在虚拟机层面,一个对象就已经创建完成了,不过在JAVA层面上,此时并未执行<init>指令,所以此时都是零值,因为内存空间初始化, 等待执行<init>指令,我们的对象就会变成我们所需要的了。


2、对象内存布局

一个创建好的对象里包含有对象头、指针、实例数据、对齐补充, 如果这个对象是数组的话,还包含有数组长度。在JAVA中查看对象内存布局以及占用字节情况,需要用到JOL(Java Object Layout)

   <dependency>
        <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.16</version>
    </dependency>

                                                图十一 对象在内存中的布局

在内存布局中,每个不同的元素都占用字节,而这些字节必定是能整除8的数字!这是缓存行对齐,缓存行对齐的深入在JUC详解。

对象头

对象头中包含当前锁的状态、分代年龄、锁标志位、哈希码......等等

                                                        图十二 Hotspot的实现对象头

根据不同的锁状态,包含的元素都有所不同。

比如一个 Object o = new Object,所占的字节是多少?

使用JOL工具之后观察

object head :mark 对象头,占用8字节

object header : class 类型指针,占用4字节,这个是因为JVM默认开始压缩指针。

object alignment gap 对齐补充,将12字节对齐16字节。


3、对象访问定位

因为所有对象是保存在JAVA堆中的,所以我们访问这个对象就需要用到JAVA栈(虚拟机栈)里的引用reference,访问JAVA堆里的对象有两种方式:

  1. 句柄访问

    1. 句柄访问的方式,实际上实在堆中占用一块内存变为句柄内存池,而句柄池里存放的对象实例数据的指针,所以当我们需要访问对象的时候,从JAVA栈直接引用句柄池然后通过对象指针找到对应的数据。

  2. 直接指针引用

    1. 直接指针引用:其实引用中直接就是存的对象数据的指针,会很快捷,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的 相关信息。

  3. 两种访问定位的异同:

    1. 在对象创建访问频繁的情况下,使用直接指针引用会更加高效,因为使用句柄会分为两个步骤,从而减少效率。

    2. 使用句柄的方式,则对象被移动、回收时,只需要修改句柄池里的指针,而与JAVA栈的引用无关。

    3. 两种方式在整个软件环境下的不同而使用不同的方式。

参考文献

《深入理解Java虚拟机》 (第二版) 周志明 著;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值