深入理解jvm-读书笔记-HotSpot虚拟机对象

HotSpot虚拟机的对象

1.对象的创建

SpotHot虚拟机对象的创建流程大体上是这样的,虚拟机在碰到一条new语句的时候,先查看指令的参数在常量池是否有对应类的符号引用,如果没有则进行类加载,解析和初始化的过程将类加载入虚拟机。然后在加载检查通过之后,就可以为对象分配内存了。在加载完类之后一个对象的内存空间大小就已经能确定了。分配空间时存在两个需解决的问题:

如何确定要分配的内存到底在堆上的哪一块?
要是堆上的已使用的和未使用的内存都是连续的的话,那就很好解决了——只要把这两块内存之间的那个地址加上这个对象所需要分配的空间就ok了,这就是“指针碰撞”的分配方式。对于那些使用带整理的算法的收集器,如serial,parnew收集器,这种方式很简单也很有效。但是 对于使用Mark-Sweep算法的cms收集器就没法这样了,标记清除算法并不要求两块内存是连续的,cms收集器常用的是“空闲列表”的分配方式,空闲列表就是虚拟机需要维护一个列表,来记录哪些内存块时可用的,然后在需要分配内存的时候选择一个适合的内存块去存放。

如何在内存分配以及引用绑定的过程中实现同步?
java中对象的创建是非常频繁的行为,所以很有可能在给先创建的对象A分配了空间,但还没有来得及修改指针,就又使用原来的指针来给对象B分配空间。解决这个问题有两种办法,一个是对堆内存空间进行加锁,对分配空间的操作实行同步处理,另一种就是通过对每个线程进行预先的分配一小块内存,称为Thread Local Allocation Buffer(本地分配缓冲),这实际上就是通过把要分配的内存变成线程私有,来避免对整个空间实行同步,不过在TLAB无法分配足够的内存给新对象时,就还是需要在对整个堆加锁的前提下为TLAB扩容了。

在给新对象分配了内存之后,虚拟机就需要将分配的内存空间进行初始化为0(不包括对象头),这样就算不为对象的实例数据赋初值也可以直接使用了。不过如果使用的是TLAB,那初始化的操作就会前提到TLAB分配的时候。

在进行初始化之后就需要对对象进行必要的设置了,例如此对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等,这些信息存放在对象头之中。

在这些动作完成之后,从java虚拟机的角度来看一个新的对象已经产生了,但从Java程序的角度来看,对象的创建才刚刚开始,上面只是完成了new指令,而一个对象实例化完成还需要使用invokeSpecial来调用方法,把对象按程序员的意愿来赋值,到这里才算是一个对象的创建完全完成了。

通过上面我们可以看出对象的创建所对应的new XX();Java代码翻译成字节码指令实际上有两步,一步是为对象分配空间并初始化空间的new指令,还有一步则是根据构造方法,即方法为实例变量赋值。

2.对象的内存布局

在hot spot虚拟机中,对象内存空间可以分为三部分,分别是对象头,实例数据域,对齐填充。对象头存放哈希码,分代年龄,指向类的指针等等。实例数据域则是用来存放实例数据的,包括本身的以及从父类那继承来的,顺序并不会完全按照定义的顺序,还会受分配策略影响。

3.对象的访问定位

创建对象是为了使用对象,而使用对象则需要先在堆中找到对象,访问定位则是描述的如何找到对象。对象的访问定位指的是对象的引用如何去找到堆中的对象的具体位置,不同的虚拟机有不同的实现方式,但主流的是句柄和直接指针两种方式。注意访问一个对象的某个域不仅仅只需要知道对象在堆内存中的地址,还需要知道描述对象数据的结构的类型信息,如字段表,类型信息放在方法区中。

句柄方式是指虚拟机维护一个句柄池,池中的每一个元素都包含了一个到对象的实例数据的指针和到对象类型数据的指针。句柄方式的优点是在对象被移动的情况下(如垃圾收集时移动对象)只需要修改句柄池中实例数据的指针就行了,而reference本身无需修改。

直接指针方式是直接指向java堆中的实例数据信息地址的指针,而指向对象的类型信息的指针则是存放在java堆中的实例数据中。直接指针的优点就是非常快,它比句柄方式少一次指针定位的开销,这些开销集小成多也会影响应用的性能。

猜你喜欢

转载自blog.csdn.net/qq_41999695/article/details/89263229