二、虚拟机在java堆中对象分配、布局和访问的过程
- 一、 对象的创建
- 1、从java程序,new指令开始,从虚拟机来看,则是判断类是否被加载;
- 2、类加载通过后,有两个问题:
- 一、内存分配,
- 对象所需内存的大小在类加载完成后就可以完全确定,为对象分配空间的任务等于把一块确定大小的内存从Java堆中划分出来。
- 两种方法:
- 指针碰撞
- 空闲列表
- 分配方式的选择取决于:java堆是否规整;Java堆是否规整取决于:所采用的垃圾收集器是否带有压缩整理功能。
- 例子:
- 在使用Serial、ParNew等带有Compact过程的收集器时,系统采用分配算法为:指针碰撞;
- 使用CMS,基于 Mark-Sweep算法的收集器时,系统采用分配算法为:空闲列表。
- 二、对象创建的线程安全问题
- 问题描述
- 对象创建在虚拟机中的行为非常频繁,即使只是修改一个指针所指向的位置,在并发情况下也并不是线程安全的,有可能出现正在给对象A分配
- 内存,指针还没有修改,对象B又同时使用了原来的指针来分配内存的情况。
- 解决方法,有两种:
- 1 对分配空间的动作进行同步处理;
- 实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
- CAS(比较与交换,Compare and swap)是一种有名的无锁算法。(ConcurrentHashMap也用到了此算法)
- 2 本地线程分配缓冲
- 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Tlab)。
- 虚拟机是否使用Tlab,可以通过-XX:+/-UseTlab参数来设定
- 1 对分配空间的动作进行同步处理;
- 问题描述
- 一、内存分配,
- 3、对象内存分配完成后,虚拟机把内存空间初始化
- 虚拟机将分配到的内存空间都初始化为零值(不包括对象头),如果使用Tlab,初始化操作可以提前至Tlab分配时进行。
- 目的:
- 保证了对象的实例字段在java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的的零值。
- 4、修饰对象
- 虚拟机对对象进行必要的设置。其实是对象头。
- 对象头中包括:对象的哈希码、对象的Gc分代年龄、此对象是哪个类的实例、类的元数据信息的指示等;
- 根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
- 5、对象产生
- 从虚拟机的角度,一个新的对象已经产生。
- 从java程序的角度,对象创建才刚刚开始,init方法还没有执行,所有的字段都为零。
- 一般来说(由字节码中是否跟随invokespecial指令所决定),执行new 指令之后会接着执行方法,
- 把对象按照程序员的意愿进行初始化,这样一个真正可用的对象产生。
- 二、 对象的内存布局
- 对象在内存中布局分配分为三个部分
- 对象头
- 实例数据
- 对齐填充
- 1 、对象头
- 如图:
- 第一部分
- 用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,
- 第二部分
- 类型指针,即对象指向它的类元素数据的指针,虚拟机通过该指针确定对象的实例是哪个类。
- 如图:
- 2 、实例数据
- 对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
- 包括父类继承的,子类定义的;
- 存儲順序: 虚拟机分配策略参数和字段在java源码中定义的顺序 ;
- hotSpot虚拟机默认的分配策略为longs/doubles、ints、 shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),
- 相同宽度的字段被分配到一起。在这个前提下,父类中定义的变量会出现在子类之前。
- 3 、对齐填充
- 不是必须存在的,只是占位符的作用。
- 因为对象的大小必须是8字节的整数倍。对象头部分正好是8字节的倍数,当对象实例数据部分没有对齐时,通过对齐填充补全。
- 对象在内存中布局分配分为三个部分
- 三、 访问
- 访问方式有2个,句柄和指针:
- 句柄访问
- 存储位置:java 堆中划分一块内存作为句柄池,reference中存储的就是对象的句柄地址;
- 包含信息:对象实例数据与类型数据各自的具体地址信息。
- 指针访问
- reference 存储的直接就是对象地址。
- 问题: 考虑java堆对象的布局中存放访问类型数据的相关信息。
- 句柄访问
- 两种方法的优劣:
- 句柄
- reference中存储的是稳定的句柄地址,对象被移动时,只改变句柄中的实例数据指针,而reference本身不需要修改。(在垃圾收集时移动对象时普遍的)
- 指针
- 速度更快,节省了一次指针定位的时间开销。--由于对象的访问在java非常频繁。(积少成多)
- 句柄
- Sun HotSpot 采用第二种方式
- 访问方式有2个,句柄和指针: