对象的创建、内存布局、访问定位

来源:互联网 发布:网管软件 编辑:程序博客网 时间:2024/05/21 04:21

一、对象的创建

1.虚拟机遇到一个new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用2.检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有,那必须先执行响应的类加载过程3.在类加载检查功通过后,为新生对象分配内存。对象所需的内存大小在类加载完成后便可完全确定。(在虚拟机角度,此时对象已经产生,但是在Java程序员的角度来说,对象创建才刚刚开始----<init>方法还没有执行,对象各字段都为零,所以在new之后紧接着,执行<init>方法,把对象按照程序员的意愿进行初始化之后,才算是一个对象完全的产生)

对象内存分配算法分为:
指针碰撞(内存规整,使用过的内存放在一边,空闲的放在另一边,中间放置一个指针作为分界的指示器,分配内存是只需将指示器向空闲内存那边挪动对象所需内存的大小即可)
使用Serial、ParNew等带Compact过程的收集器使用指针碰撞

空闲列表(Java中内存不规整,使用多的内存he空闲内存相互交错,无法使用指针碰撞,虚拟机需要维护一个列表,记录哪些内存是可用的,当分配对象内存时需要在列表中找到一块足够大的内存划分给对象,并更新列表记录)
CMS这种基于Mark-Sweep算法的收集器使用空闲列表。

问题:

对象创建在虚拟机中非常频繁,即使是修改指针的一个位置,在并发的情况下也不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B也需要使用A的指针来分配内存。

解决:
1.对分配内存空间的动作进行同步处理——实际上虚拟机使用CAS加上失败重试的方式保证更新操作的原子性。
2.把内存分配的动作,按照线程划分在不同的空间之中进行,即每个线程预先分配一块内存成为本地线程分配缓冲(Thred Local Allocation Buffer)。通过-XX:+/-UseTLAB参数来设定虚拟机是否使用TLAB

内存分配完成之后将分配到的内存空间初始化为零值(除了对象头),目的是使对象实例字段在Java代码中不赋初值就可以直接使用,程序可访问到各个字段数据类型的对应零值。

二、对象的内存布局
对象头(Header)
包括两部分信息,第一部分:对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为32 bit和64 bit(未开启压缩指针),官方称它为“Mark Word”。

第二部分:类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须有一块用于记录数组长度的数据,元数据信息。

实例数据(Instance Data)
是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。

对齐填充(Padding)
对齐填充不是必然存在的。HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的整数倍。因此,当对象实例数据部分没有对齐时,就需要通过对其补充来补全了。

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

句柄访问
Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对实例数据与类型数据的各自具体的地址信息。
句柄访问

直接指针访问
reference中存储的直接就是对象地址。

直接指针访问

阅读全文
0 0