JAVA 创建一个对象

一、对象的创建

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

在类加载检查通过后,接下来虚拟机将为新生的对象分配内存。对象所需要的内存的大小在类加载完成后便可以确定,为对象分配空间的任务等同于把一块大小确定的内存从java堆中划分出来,目前常用的有两种方式:

①指针碰撞(BumpthePointer):

假设java堆内的空间是绝对规整的,所有用过的内存都放在一边,空闲的内存放在一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅把那个指针向空闲的空间挪动一段与对象大小相等的距离。

②空闲列表(Free List):

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

除了如何划分可用空间外,在并发情况下划分不一定是线程安全的,有可能出现正在给A对象分配内存,指针还没来得及修改,对象B又同时使用了原来的指针分配内存的情况,解决这个问题两种方案:

  1、分配内存空间的动作进行同步处理(CAS):实际上虚拟机采用CAS配上失败重试的方式保证了更新操作的原子性。

  2、内存分配的动作按照线程划分在不同的空间中进行(TLAB):为每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。

二、对象的内存布局

 在HotSpot虚拟机中,对象在内存中的存储布局可以分为三块区域:对象头(Header)、 实例数据(Instance Data)、和对齐填充(Padding)。

(一)对象头

1、第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为“Mark Word”。

2、类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象属于哪个实例。

(二)实例数据

对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。 

(三)对齐填充

对齐填充不是必然存在的,也没有特别的含义,仅仅起着占位符的作用。

三、对象的访问定位

建立对象是为了使用对象,我们java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流方式有使用句柄和直接指针两种。

(一)句柄访问

java堆中将会划分出一块内存来作为句柄池,reference中存储对象句柄位置,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。 

使用句柄访问的好处是句柄中储存的是稳定的对象地址,当对象被移动时候,只需要更新句柄中的对象实例部分的值即可,句柄本身不用被移动修改。

(二)直接指针访问

如果使用直接指针访问,那么java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

使用直接指针的好处相对于句柄来讲,少了一次指针定位时间的开销,缺点是,当对象被移动时(如进行GC后的内存重新排列),对象的引用(reference)也需要同步更新。

猜你喜欢

转载自www.cnblogs.com/liuchd/p/13157264.html