虚拟机对象的创建,布局,和访问

1.概述

本文是整理《深入理解java虚拟机》(周志明著)中《2.3 HotSpot虚拟机对象探秘》的笔记总结

2.对象的创建

在语言层面上,创建对象(克隆,反序列化)通常仅仅是一个new关键字,而在虚拟机中,对象的创建需要一系列过程,下面来详细说明。

  • 虚拟机遇到一条new的指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,如果没有,需先进行类加载过程。
  • 类加载通过后,虚拟机为新生对象分配内存。(对象所需的内存大小在类加载完成后便可完全确定)。在分配内存的时候如果java堆中的内存是规整的(即已使用的内存全部在一边,未使用的内存全部在另一边),则采取“指针碰撞”分配内存————指针移动对象大小的内存空间;如果java堆中的内存不是规整的,则采取“空闲列表”分配内存————根据表中空闲区域记录的大小进行分配。而java堆中的是否规整由所采用的垃圾收集器有关。
  • 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为0值,这一步操作保证了对象的实例字段在java代码中可以不赋初始这就直接使用。
  • 执行new指令之后会接着执行方法,这样一个真正可用的对象才算完全产生出来

问题:对象创建是非常频繁的行为,在并发的情况下我们不能保证其是线程安全的,比如在一个对象分配内存时指针还未移动,另一个对象就在这个内存区域内分配空间了。为了解决这个问题,有两种方案:
1.一种是对分配内存的动作进行同步处理;
2.另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在那个线程的TLAB上分配。只有TLAB用完之后,才需要同步锁定。

3.对象的内存布局

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

  • 虚拟机的对象头包括两部分信息:第一部分是存储对象运行时数据(哈希吗,GC分代年龄,锁状态标志等)。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 实例数据部分是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容,无论是从父类中继承来的还是在子类中定义的,都需记录下来。
  • padding无具体实义,起占位符作用。

4.对象的访问定位

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

  • 使用句柄的话,java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息.如下图:

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

两者比较:
1.使用句柄来访问时,reference中存储的是稳定的句柄地址,在对象被移动时(比如在GC后进行整理)只会改变句柄中的实例数据指针,而reference本身不需要修改。
2.使用直接指针访问,最大的好处就是节省了一次指针定位的时间开销,使其访问速度更快。sun HotSpot采取了直接指针访问的方式。

原文地址:https://www.cnblogs.com/Mrfanl/p/10406683.html