了解了虚拟机内存分配过后,我们就HotSpot虚拟机和常用额Java堆为例,探索一下对象的分配、布局以及访问的全过程。
一,对象的创建
1. 此处讨论的对象不包括数组和Class对象,只包含普通Java对象。
2. 当虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,必须先执行相应的类加载过程。
3.为新生对象分配内存。如果java堆是完整的,采用“指针碰撞”分配内存,如果不是完整的,采用“空闲列表”分配内存。Serail,parNew带压缩的收集器采用指针碰撞,而CMS是采用空闲列表。
4. 在分配内存的是如何解决非线程安全的?第一,对分配内存空间的动作进行同步处理(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性)。第二,把内存分配操作按照线程划分在不同的空间进行,每个线程在堆中预先分配一小块内存,称为本地线程分配缓冲TLAB。虚拟机是否使用TLAB可以由参数设定(-XX:+/UseTLAB)
5. 接下来是初始化为零值。
6. 虚拟机对对象进行必要的设置,也就是设置对象头(Object Header),对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。根据当前运行状态的不同,对象头会有不同的设置方式。
7. 执行init方法,把对象按照程序员的意愿进行初始化。
二,对象的内存布局
1.HotSpot虚拟机,对象在内存中存储的布局可以分为三个区域:对象头Header,实例数据Instance Data,对齐填充Padding。
2. 对象头包含两部分信息:第一,存储自身的运行时数据(哈希码,GC分代年龄,锁状态标志,线程持有锁,偏向线程ID,偏向时间戳),是非固定的数据结构。第二,存储类型指针,即对象指向它的类元数据的指针,可以通过这个指针确定这个对象是哪个类的实例。但并不是所有的虚拟机实现都必须在对象数据上保留类型指针。如果对象是Java数组,还存储数组长度。
3. 实例数据是对象真正存储的有效信息,也就是定义的各种字段的内容。
4. 对齐填充不是必然存在的,对象必须是8字节的整数倍,当前面两部分不满足时就会有对齐填充。
三,对象的访问定位
1.我们需要通过栈上的reference数据来操作堆上的具体对象,目前主流的访问方式有两种:使用句柄和直接指针。
2. 使用句柄,java堆中将会划分出一块内存来作为句柄池,Reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
3. 使用直接指针:Java堆对象必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
网友评论