那么,我们还要考虑一个问题,即在多线程的情况下,只有一个指针怎么能确保一个线程分配了内存指针没修改的时候另一个线程又分配内存不会覆盖之前的内存呢?这里有一种方法,让每一个线程在堆中先预分配一小块内存(TLAB本地线程分配缓冲),每个线程只在自己的内存中分配内存。
最后,对象被成功分配内存。我们知道通过一个对象,我们可以通过getClass()方法获取类,默认比较两个对象实际比较的是对象内存的哈希值,这又是怎么实现的呢?其实在分配完内存后,虚拟机会对对象进行必要的设置,对象的类,对象的哈希码等信息都存放在对象的对象头中,所以分配的内存大小绝不止属性的总和。
三. 对象的内存布局对象在堆中的布局分为三个区域:对象头,实例数据,对齐填充。
对象头 包括两个部分,第一部分用于存储自身运行时的数据例如哈希码,锁状态,哪个线程可以拥有。第二部分存放指向方法区的类元数据。
实例数据 存放类的属性信息,包括父类的属性信息。
对齐填充 这是虚拟机要求对象起始地址必须是8字节的整数倍,可以说对齐填充没有什么特别的含义。
四. 对象的访问定位我们知道,引用是引用,对象实例是对象实例。引用存放在虚拟机栈中,数据类型为reference,对象实例存放在堆中。那么引用是如何指向对象实例的呢?
主流的访问方式有两种,第一种是通过句柄池,如果使用句柄池,那么java堆中将会划分出一部分内存作为句柄池,句柄包含对象类型指针指向方法区的类型信息,还有对象实例指针,指向堆中的实例地址。
第二种是reference引用直接指向堆中的对象实例,对象实例的对象头存放对象类型指针。
两种方法各有优势,第一种可以在对象实例在GC时移动的时候只改变句柄池中的对象实例指针,而不用改变reference引用本身。第二种方法就是访问速度快,减少了一次指针定位的时间开销。目前HotSpot虚拟机就采用的第二种方式。
总结了解java内存区域是对java的深入学习,以前只知道有堆和栈的区分,现在我们了解到了具体的堆栈的作用。内存是怎么划分的,对象是怎么存储的,方法和属性的存放区别。通过对这些内容的了解,会让我们写java程序更加游刃有余,有的放矢。