您的位置:首页 > 其它

深入理解JVM(二)--HotSpot虚拟机对象探秘

2018-01-16 10:52 246 查看
实用优先,先从常用的虚拟机HotSpot和常用的内存区域Java堆为例,深入探讨HotSpot虚拟机在Java堆中对象分配、布局、访问的全过程。

对象的创建

Java是一门面向对象的编程语言。在Java程序运行过程中无时无刻都有对象被创建出来,在语言层面上,创建对象仅仅是一个new关键字而已,而在虚拟机中,对象的创建是一个复杂的过程。(这里的对象是指Java中的一半对象,不包括数组和Class对象。)

虚拟机遇到一个new指令时,首先会检查这个指令的参数是否能在常量池中找到一个类的符号引用,也就是说会先去检查这个类有没有被加载过,如果没有,当然是会先去执行加载类的过程。

在类检查通过后,接下来虚拟机将会为新生对象分配内存,对象所需的内存大小在类加载完成后就已经完全确定。为对象划分内存的过程,就相当于把一块确定大小的内存从Java堆中划分出来。

若JVM堆中的内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在一边,中间放着一个指针作为指示器,那么分配内存就是指把指针向空闲空间挪一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。

若JVM堆不是规整的,已使用的内存和空闲内存相互交错。那就没有办法简单的进行指针碰撞了。JVM会去维护一个列表,记录那块内存是可用的,在分配的时候就会找到一个与对象所需内存大小符合的内存块,并更新列表上的记录,这种分配方式称为空闲列表(Free List)。

选择哪种分配方式是由JVM堆是否规整决定,而JVM堆是否规整又是由它所采用的垃圾回收器是否带有压缩整理功能决定。

创建对象在JVM堆是非常频繁的行为,即使仅仅是一个指针移动,也会带来并发情况下数据不安全的问题。解决并发不安全的问题有两种:一种就是J
b4a3
VM采用的CAS配上失败重试的方式来保证更新操作的原子性。这时就相当于进行了同步处理。另一种就是让每个线程在JVM堆中预先分配一小块内存,让他们在不同的空间中 进行,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。那个线程需要分配内存,就在那个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UserTLAB来设定。

内存分配完成后,JVM要将分配到的内存空间都初始化为零。

接下来,JVM要对对象进行必要的设置,设置这个对象是那个类的实例,如何找到类的元数据,对象的哈希码,对象的GC年龄分配等。

执行new指令之后会紧接着执行初始化方法,把对象按照程序员的意向进行初始化操作,这样。一个真正可用的对象才算完全生产出来。

对象的内存布局

JVM中内存布局可以分为3个区域:对象头、实例数据、对齐填充。

对象头又分为两部分信息:第一部分存储对象自身的运行时数据。比如哈希码、GC年龄、锁状态、线程持有的锁、偏向线程的ID、偏向时间戳等。这部分长度分别为32bit和64bit以对应不同的位数虚拟机。第二部分是指针类型。JVM通过这个指针来确定这个对象是哪个类的实例。如果这个对象是数组,那在对象头中还有一块用于记录数组长度的数据。

实力数据是对象存储的真正有效的数据。无论是从父类中定义下来的,还是自身本身就存在的。都会记录下来。

对齐填充不是必须存在的,仅仅是占位符的作用。由于JVM的内存管理系统要求对象的起始地址必须是8字节的整数倍,当对象实例数据部分没有对齐时,就需要通过对其填充来补齐。

对象的访问定位

创建对象是为了访问对象,JVM是通过栈上的引用数据来操作堆上的具体对象的。目前主流的访问方式有两种:句柄操作和直接指针操作。

句柄操作时,句柄中包含了对象实例数据与类型数据各自的具体地址信息。



如果使用直接指针访问对象,那么引用中存储的直接就是对象地址。



两种对象访问方式各有优势:

使用句柄访问好处就是引用中存储的是稳定的句柄地址。在对对象被移动时,只会改变句柄中的实例数据指针,而引用本身不需要改变。

使用直接指针访问的好处是速度更快。因为它节省了一次指针定位的时间开销。 由于对象访问在JVM中非常频繁。因此这类开销积少成多就会是一项非常值得注意的成本。

就Sun HotSpot来说。就是使用的后者来进行对象访问。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: