本文全部内容均转述自《深入理解Java虚拟机 JVM高级特性与最佳实践(第二版)》2.3节 下载地址:http://pan.baidu.com/s/1jIFqx74 密码:t517
主要讲述的内容主要是在常用的HotSpot虚拟机中,对象是如何在堆内存中分配,布局和访问的。
分配
Java面向对象,时时刻刻有对象被创建出来。在语法上,我们通过new关键字可以创建一个新的对象。而当虚拟机遇到new时,会通过以下步骤创建一个对象,并为之分配内存:
- 【类加载检查】检查常量池是否能定位到
类的符号引用。如果对应的类没有被加载、解析和初始化,则执行相应过程。 - 【分配内存】在前一个步骤已经知道类对应的对象所需的内存大小,这一步要做的是
找到合适大小的内存空间。 - 【内存空间初始化】初始化为
零值。 - 【对象头初始化】把包括
对象是哪个类的实例、如何找到类的元数据信息、对象哈希码、对象的GC年代等信息保存到对象头中。 - 【执行init方法】根据对象初始化构造参数,调用相应的
<init>方法,把字段设为需要的值(而非零值)。
##分配内存的细节
在分配的过程中,我们再来讨论下2. 内存分配的细节。
###分配方法分类
笼统来说,内存分配方式可以分两种:
-
指针碰撞(Bump the Pointer)。 这种方式可以理解成把所有内存都当成一个整体,中间用一个指针摆在中间,左边全是用过的内存,右边全是没用过的内存,需要用的指定大小的内存时,左移分配内存。使用这种分配方式垃圾收集器包括:
Serial、ParNew等。 -
空闲列表(Free List)。内存被分为一块块的空间,通过一个单独的列表来记录内存中哪些内存块空闲,哪些已被使用。使用这种肥配方是的垃圾收集器包括
CMS等。
###分配时的线程安全
由于这里讨论的堆内存是被多线程中共享的,所以在并发的过程中必须考虑线程安全问题。同样也有两种方案:
- 同步。同步控制每一个内存分配动作。
- 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