当Jvm遇到new关键字

2019-06-23 11:57发布

在上一篇我们知道了Jvm的内存划分,这一篇我们来具体分析一下,当我们用new关键字创建一个新对象时,Jvm都做了哪些工作。当虚拟机执行到new指令时,发现它是关键字,于是会检查这个指令参数是否能在常量池中找到相关的引用,并检查这个引用所代表的类是否被加载、解析和初始化过。如果没有被加载,则先执行类的加载过程。当类加载成功后,虚拟机就会为新创建的对象分配内存。分配内存无非就是在Java堆上划出一部分区域来作为新对象的存储空间。但在实际的处理时是比较麻烦的。例如怎么找到空闲的内存,如何划分固定大小内存。我们知道Java虚拟机规范中所说虚拟机中的堆内存可以是连续的也可以是不连续的。所以虚拟机在为对象分配新内存时,就要根据这两种不同的方式来采取不同的实现。这两种方式分别叫作:指针碰撞和空闲列表。正面我们分别看一下它们具体的实现方式。

  • 指针碰撞:所谓的连续内存是指Java堆中的内存是绝对规整的,用过的内存在一边,空闲的内存在另一边。中间有个指针作为分界点,这时如果要分配新内存,只要指针向空闲的内存一方移动一下就可以了。这种分配内存的方式就叫指针碰撞。
  • 空闲列表:如果Java堆中的内存并不是完整的,也就是不是连续的。这时使用的内存和空闲的内存没有任何规则,无法用指针碰撞的方式,来分配内存。这时虚拟机只能采取其它办法来标识出哪些内存是使用的,哪些内存是空闲的,所以虚拟机就要维护一个列表,用来存储哪些内存是空闲的,分配内存时,只要从列表中划分一块区域存储对象实例,并更新列表上的记录就可以了。这种方式就叫空闲列表。

由于不同的虚拟机所采用的内存分配方式是不同的,所以上述主要说明两种分配内存的方式,虚拟机到底采用哪种方式由不同虚拟机的堆自行决定。

上述的过程只是给对象分配了相关的内存空间,但对象还是空的,内存数据要怎么保存对象的信息呢?在虚拟机中对象在内存中存储主要分3个区域:

  • 对象头:主要包括两部分一个是对象自己的运行数据如哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID等官方称知为Mark Word。对象头的另一部分就是类型指针,也就是对象指向类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。对象头的信息是不固定的,如果对象是一个数组,那么在对象头中就会有记录数组长度的信息。

  • 实例数据:也就是程序中自定义的各种字段的类型和内容,包括父类或子类的数据都在这里保存,这是对象真正的存储的有效信息。
  • 对齐填充:主要的作是就是占位符。因为虚拟机要求对象起始地址必须是8字节的整数倍也就是对象大小必须是8的整数倍,因此,如果对象的实例数据没有对齐时,就会需要通过对齐填充来补齐。

对象现在已经创建完了,但它是存储在堆上的,为了方便我们操作堆中的对象,Java是通过栈上的引用数据来操作堆上的具体对象的。这个引用是使用直接指针访问的。堆中要存储这个指针的相关信息,而引用存储的就是该对象的内存地址。

以上就是在创建一个新对象时Jvm对内存的主要操作。因为不同的虚拟机可能有不同的处理逻辑,上述中所描述的虚拟机都指的是Sun HotSpot虚拟机。

文章来源: https://www.toutiao.com/group/6705573091906421261/