对象内存怎么分配?
在类加载检测通过后, VM就会为新生对象分配内存。为对象分配内存的操作,就是将内存划分一块大小和对象相等的内存空间。但是内存空间并不是绝对规整的,所以分配内存的过程就会有多种方式。当空闲内存和非空闲内存完全分开的情况下,即一边是空闲,另一边是非空闲,你们就会通过中间的指针滑动的方式分配,被称为“指针碰撞”。但是如果空闲的内存,零散的发布,就不能使用指针来操作了,VM就必须通过维护一个列表,记录上哪些内存块时可用的,在列表中找到一块足够大的空间划分给对象实例,这种方式被称为“空闲列表”。具体使用哪种分配方式,有Java堆的规整程度,和GC是否带有空间压缩整理的能力决定。
当然在实际划分的时候,还需要考虑一个问题:在虚拟机中创建对象是非常频繁的行为,任何修改操作,在并发的情况下也不是线程安全的。如果要解决这个问题,有2个方案。一种是对分配内存空间的动作同步处理,另一种是把内存分配的动作按照线程划分在不同的空间,每个线程空间都独立,每个线程在堆中预先分配一块内存(Thread Local Allocation Buffer)TLAB 本地线程分配缓冲。如果使用TLAB,可以通过-XX: +/-UseTLAB 来设定。
空间分配完成,VM就会将分配的内存空间(不含对象头)都初始化为零值。这步保证了对象的实例字段不赋初始值就可以直接用,访问这些字段类型所对应的零值。
对象的内存结构?
在HotSpot的虚拟机,对象在堆中的布局分为3个部分:对象头(Header),实例数据(Instance Data)和对齐数据(Padding)。
对象头包括2类信息,第一类是存储对象自身的运行时数据,哈希码HashCode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分被称为MarkWord在32位和64位机器上分别占32个bit和64bit。另一类是类型指针,即对象指向它的类型元数据的指针,JVM通过这个指针来确定该对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针。如果对象是一个JAVA数组,那在对象头中还会有一块用于记录数组长度的数据。
实例数据部分,是对象真正存储的有效信息,即我们再程序代码里面定义的各种类型的字段内容。
第三部分是对齐填充,这不是必然存在的,没有特别含义,仅仅起着占位符的作用。
对象怎么找?
如何定位对象?
Java程序会通过栈上的reference数据来操作堆上的具体对象。实际上主流的访问方式有句柄和直接指针2种。
使用句柄:
Java堆中将可能一块内存作为句柄池,reference中存储的对象的句柄对象,而句柄就包含了对象实例数据与类型数据各自具体的地址信息
使用直接指针:
reference存储的是对象地址,如果只访问对象,就不用多一次间接访问的开销。HotSpot使用的就是这个方法