java对象创建过程

来源:互联网 发布:机械设计三维图软件 编辑:程序博客网 时间:2024/06/07 17:22

1、对象创建的整个流程:

step1: 当虚拟机遇到一个new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否被加载、解析和初始化过。如果没有,那么必须先执行相应的类加载过程。

step2: 类加载检查过后,方法信息、常量、静态变量等保存在一块称为方法区的内存中,接下来虚拟机将为新生对象分配内存。对象所需内存大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。

step3:接下来虚拟机要对对象进行必要的设置,例如这个对象是那个实例、如何能找到类的元数据信息、对象的哈希码等。这些信息放在对象头中。

step4: 上面工作完成后,所有的字段都为零,从虚拟机的角度看对象已经创建完成,从java程序的角度看没有初始化,接下来开始执行<init>方法

在堆中分配内存空间策略

根据使用不同类型的垃圾回收器对应两种策略:指针碰撞、空闲列表。

指针碰撞:当虚拟机使用具有压缩整理功能的垃圾回收器时,java堆中的内存是规整的,即使用过的内存放在一边,未使用的内存放在一边,这样分配内存空间时,只需要将那个分界使用和未使用内存的指针向未使用空间方向移动一段与对象大小相等的距离。这样称为指针碰撞。

空闲列表:当java堆中的内存不是规整的,即垃圾回收器没有压缩整理功能,堆中已使用和未使用内存空间交错,虚拟机这时就必须得维护一个列表,记录哪些内存块可用,再分配时找到一个合适大小的内存块分配,这样称为空闲列表法。

内存分配时的线程安全问题

在堆中给对象分配内存空间时还存在线程安全问题。
问题:由于在多线程情况下给对象分配内存,可能出现给A正在分配内存空间,指针还未来得及修改,对象B又同时使用了原来指针进行内存分配
解决方案:
1. 对分配内存空间动作处进行同步处理;
2. 把内存分配动作在不同的空间中进行;即为每个线程在堆中预先分配一块小内存,称为本地线程分配缓冲区TLAB。每个线程都有一个独立的线程缓冲区,当TLAB使用完并重新分配TLAB时,才需要同步锁定。

2、对象的内存布局(HotSpot虚拟机)

对象在内存中的布局可以分为3块:对象头、实例数据和对齐填充。

1)对象头

HotSpot虚拟机的对象头包含两部分信息
1. 用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志、线程持有的锁、偏向线程ID等,这部分数据在32位机中是32bit,64位中64bit,官方称为Mark Word
2. 类型指针,即对象指向它的类元数据的指针(也就是在方法区中存储的类)

2)实例数据

接下来的实例数据部分是对象真正存储的有效信息,无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。

3)对齐填充(HotSpot中必须是8的整数倍)

3、对象访问定位

java通过栈上的reference数据来操作具体堆上的对象。reference在java虚拟机规范中只规定了一个指向对象的引用,并没有具体说明如何去寻址定位堆中具体对象,所以对象的访问方式取决于具体虚拟机的实现。

目前主流的访问方式有两种:使用句柄、直接指针

1)使用句柄

如果使用句柄,java堆中将会专门划分出一块内存作为句柄池,reference的值就是对象的句柄池地址,句柄中包含了对象实例数据(指向堆内存区)对象类型数据(如:静态变量,会指向方法区)各自的具体地址信息。
这里写图片描述

2)使用直接指针访问

直接指针访问时:reference存储的就是对象在堆中的内存地址,而对象类型数据的地址(也就是对应方法区中对象的地址)存储在堆对象中。
这里写图片描述

两种存储方式对比:

  1. 使用句柄存储最大的好处就是reference中存储的是稳定的句柄地址,当对象被垃圾回收器移动时,只需要更改句柄中的实例数据指针,不需要操作reference值。
  2. 使用直接指针最大的好处就是速度快,只用寻址一次,不用句柄二次寻址,效率十分高。Sun HotSpot虚拟机就是使用直接指针。