本文全部内容均转述自《深入理解Java虚拟机 JVM高级特性与最佳实践(第二版)》2.3节 下载地址:http://pan.baidu.com/s/1jIFqx74 密码:t517

主要讲述的内容主要是在常用的HotSpot虚拟机中,对象是如何在堆内存中分配,布局和访问的。

分配

Java面向对象,时时刻刻有对象被创建出来。在语法上,我们通过new关键字可以创建一个新的对象。而当虚拟机遇到new时,会通过以下步骤创建一个对象,并为之分配内存

  1. 【类加载检查】检查常量池是否能定位到类的符号引用。如果对应的类没有被加载解析初始化,则执行相应过程。
  2. 【分配内存】在前一个步骤已经知道类对应的对象所需的内存大小,这一步要做的是找到合适大小的内存空间
  3. 【内存空间初始化】初始化为零值
  4. 【对象头初始化】把包括对象是哪个类的实例如何找到类的元数据信息对象哈希码对象的GC年代等信息保存到对象头中。
  5. 【执行init方法】根据对象初始化构造参数,调用相应的<init>方法,把字段设为需要的值(而非零值)。

##分配内存的细节 在分配的过程中,我们再来讨论下2. 内存分配的细节。

###分配方法分类

笼统来说,内存分配方式可以分两种:

  1. 指针碰撞(Bump the Pointer)。 这种方式可以理解成把所有内存都当成一个整体,中间用一个指针摆在中间,左边全是用过的内存,右边全是没用过的内存,需要用的指定大小的内存时,左移分配内存。使用这种分配方式垃圾收集器包括:SerialParNew等。 JVM hotspot虚拟机对象探秘

  2. 空闲列表(Free List)。内存被分为一块块的空间,通过一个单独的列表来记录内存中哪些内存块空闲,哪些已被使用。使用这种肥配方是的垃圾收集器包括CMS等。 JVM hotspot虚拟机对象探秘

###分配时的线程安全

由于这里讨论的堆内存是被多线程中共享的,所以在并发的过程中必须考虑线程安全问题。同样也有两种方案:

  1. 同步。同步控制每一个内存分配动作。
  2. Thread Local Allocation Buffer, TLAB。为不同线程划分特定的内存空间,一般情况下线程只在自己的分配空间内操作,只有TLAB空间用完才同步锁定,进行新的全局分配。

#布局

对象在内存中存储的布局分为三块

对象头

存储对象自身的运行时数据:Mark Word(在32bit和64bit虚拟机上长度分别为32bit和64bit),包含如下信息:

  • 对象hashCode
  • 对象GC分代年龄
  • 锁状态标志(轻量级锁、重量级锁)
  • 线程持有的锁(轻量级锁、重量级锁)
  • 偏向锁相关:偏向锁、自旋锁、轻量级锁以及其他的一些锁优化策略是JDK1.6加入的,这些优化使得Synchronized的性能与ReentrantLock的性能持平,在Synchronized可以满足要求的情况下,优先使用Synchronized,除非是使用一些ReentrantLock独有的功能,例如指定时间等待等。
  • 类型指针:对象指向类元数据的指针(32bit-->32bit,64bit-->64bit(未开启压缩指针),32bit(开启压缩指针))
  • JVM通过这个指针来确定这个对象是哪个类的实例(根据对象确定其Class的指针)

##实例数据

对象真正存储的有效信息

##对齐填充

JVM要求对象的大小必须是8的整数倍,若不是,需要补位对齐

Tips——对象起始地址必须是8字节的整倍数,即对象大小必须是8字节的整倍数。

访问

Java虚拟机规范中指定了虚拟机要访问对象的时候是通过Java虚拟机栈中的reference来获取对象地址的。但是并没有指定reference如何保存以及通过它具体如何获取对象地址。所以各家有各家的实现方案。

主要分两种,一种是通过句柄(保存在Java堆特定的句柄池中)间接访问,一种是直接在虚拟机栈中保存对象地址直接访问。HotSpot使用后一种方案。

转载于:https://my.oschina.net/djzhu/blog/993928

相关文章: