【JVM虚拟机】从虚拟机层面了解对象的创建与内存分布

Java是一门面向对象的编程语言,在Java程序中无时无刻都有对象被创建出来并使用。在语言层面,创建对象仅仅是使用一个new关键字,而在虚拟机中却发生了一系列的事情,今天就来从虚拟机层面了解对象的创建等一系列知识。

一、对象的创建

1、内存空间分配方式

  1. 首先虚拟机在发现new关键字后就会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号代表的类是否已经被加载、解析和初始化过,若没有就要进行类加载。
  2. 若加载了类,虚拟机就会为对象分配内存,对象的大小在虚拟机加载完成后就可以确定
  3. 分配空间等同于把内存中一块空间拿出来使用。如果假设堆中内存绝对规整,且使用的空间放一边空闲的空间放另一边,中间有一个指针指示,那么分配内存可以看作将中间指针向空闲一边移动对象所需要的空间的大小。这种方式称为指针碰撞
  4. 如果堆中的内存不规则,那么虚拟机就要维护一个表来记录那些内存块是可用的,分配后更新列表上的记录。这种方式为空闲列表
  5. 选用哪种方式有Java堆是否规整决定,而是否规整由采用的垃圾收集器是否带有压缩整理的功能功能来决定。

2.分配空间时的线程安全

因为对象的创建在虚拟机中是非常频繁的事情,因此在并发情况下就存在线程不安全的情况。比如一个线程正在给对象A分配内存,中间指针还来不及修改时,另一个线程又为对象B按照原来的指针来分配空间,就导致了线程不安全。解决这个问题有两个方案:

  1. 对分配内存空间的动作进行同步处理——实际上,虚拟机采用CAS配上失败重试方式保证了更新的原子性。
  2. 将内存分配的动作按照线程划分到不同区域进行,这样每个线程就在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。每个线程都在自己的TLAB上分配,只有自己的TLAB用完需要分配新的TLAB时才需要同步锁定。

3.初始化零值

在分配完内存后,虚拟机就需要对分配到的内存空间进行初始化为零值,如果使用TLAB,这一工作过程可以提前至TLAB分配时进行,这就使得我们在编写Java代码时可以不赋初值就直接使用,获得的就是对应类型的零值。

4.设置对象信息

初始化零值以后,就要对对象进行必要的设置,包括这个对象是哪个类的实例、对象的哈希码、对象的GC分代年龄等信息。这些信息都存放在对象头中,根据虚拟机当前状态不同等因素会对对象头有不同的设置方式。

5.init方法的执行

在完成以上工作后,从虚拟机的视角看,一个新的对象已经产生了(分配内存完成),但从Java程序的视角来看(构造器),对象创建才刚刚开始。因为现在所有的字段都为零,所以一般来说执行new指令后还会接着执行< init >方法来按照程序代码对对象进行初始化
至此,一个真正可用的随想才算完全创建出来。

二、对象的内存布局

在虚拟机中,对象在内存中存储的布局可以分为3块区域对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)

  • 对象头:对象头中包括两部分信息。一部分是存储对象自身的运行时数据,包括对象是哪个类的实例、对象的哈希码、对象的GC分代年龄、锁状态标志、线程持有锁等信息,这部分被设计成非固定的数据结构以便更好的存放更多的信息;另一部分是类型指针,就是对象用来指向它的类元数据指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个数组,那么在对象头中还必须记录好数组的长度,这是因为虚拟机可以可以通过普通对象的元数据类型确定对象的大小,但对于数组类型的对象却不可以。
  • 实例数据:实力数据用于存储真正的有效信息,也是在代码中定义好的字段内容。通常相同字宽的字段总是被分配到一起,在满足这个前提的情况下,父类定义的变量会出现在子类之前。
  • 对齐填充:不是必然存在的,仅仅起着占位符的作用,由于虚拟机的规则对象的大小必须是8个字节的整数倍而对象头部分正好是8字节的倍数,因此大概对象的实例数据没有对齐时,需要通过对齐填充来补全。

以上就是自己的学习记录,参考《深入理解java虚拟机》一书,有任何问题欢迎指正。欢迎小伙伴们点赞关注一起进步。

发布了62 篇原创文章 · 获赞 28 · 访问量 6047

猜你喜欢

转载自blog.csdn.net/Moo_Lavender/article/details/104354170