Java对象的创建过程、内存布局以及定位

HotSpot的对象

对象的创建过程

  1. 当虚拟机遇到一条new指令时,会先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载、解析和初始化过。如果没有,则先去执行相应的类加载过程。

  2. 接下来会为对象分配内存,对象所需的内存大小在类加载完成后就可以确定。分配内存的方式:

    • 指针碰撞:前提是java堆中内存是绝对规整的,已分配的内存在一边,未分配的内存在另一边,中间有一个指针作为分界值,则分配内存时只需将指针移动一段相应的距离即可。
    • 空闲列表:如果堆的内存并不规整,虚拟机就需要维护一个列表来记录哪些内存块时可用的,然后在分配内存的时候找到一块足够的内存空间分配给对象实例并更新列表。

    具体使用哪种内存分配方式由GC是否带有空间压缩整理功能(将堆空间变得规整)来决定,故当使用Serial、ParNew等带压缩整理过程的收集器时,系统采用指针碰撞的方式,而使用CMS等无整理过程的收集器时,就只能使用空闲列表的方式。

    分配内存的过程会有并发安全问题(比如说并发的修改指针),解决方式:

    • 采用CAS和失败重试这种同步方式来保证更新操作的原子性
    • 每个线程在Java堆当中预先分配一小块内存(本地线程分配缓冲TLAB),当需要分配内存时会先从TLAB中分配,TLAB用完了再通过同步锁定的方式去分配内存。
  3. 将分配到的内存空间(不包括对象头)都设置为零值,从而保证了对象的实例字段不赋初值也可以直接使用,能访问到零值。

  4. 为对象(头)进行必要的设置,如:这个对象是哪个类的实例、GC信息、哈希值等。

  5. new指令执行之后会接着执行()方法,按照程序员的意愿对对象进行初始化。

对象的内存布局

在这里插入图片描述

其中,markword是8个字节,记录GC和锁的相关信息;pointer是类型指针(指向class类型),含义是它是哪个类的实例,未压缩是8个字节,压缩后是4个字节(默认压缩),两者合起来是对象头(如果是数组则对象头中还包含一个4字节的数组长度);如果该对象的字节数不是8的倍数,需要用空字节补齐至8字节的倍数,因为64位的操作系统一次读64位也就是8个字节的效率最高;实例数据中指向普通对象的指针默认也是开启压缩的,占4个字节。

64位系统中的MarkWord如下:

在这里插入图片描述

对象的定位访问

我们可以通过对象引用reference来操作堆上的具体对象,这个定位对象的过程通常有以下两种方式:

  1. 使用句柄:在java堆当中划分出一块内存来用作句柄池,reference中存储的就是对象的句柄地址,而而句柄中包含了对象实例、类型具体的地址。
  2. 直接指针:reference中存储的就是对象的地址,但是Java堆中的对象的内存布局就需要存放类型数据相关的信息。

使用句柄的好处是在对象被移动(GC时移动对象非常普遍)时reference本身不需要修改,只需要修改句柄中实例数据的地址即可。而使用直接指针的好处是定位对象实例只需要一次指针定位即可,速度更快。很显然,HotSpot使用的是直接指针的方式

据的地址即可。而使用直接指针的好处是定位对象实例只需要一次指针定位即可,速度更快。很显然,HotSpot使用的是直接指针的方式

Guess you like

Origin blog.csdn.net/zhang_qing_yun/article/details/119192136