深入理解Java虚拟机(第三版)-02-HotSpot虚拟机对象探秘(1)

微信公众号:[攻城狮老张]
又稳又重的老码农,分享工作生活经验。
风趣幽默的段子手,总结编程专业知识。
希望能用通俗易懂的语言,给小伙伴带来收获~
[如果觉得对您有帮助,欢迎关注,转发,点赞!]

以下大部分内容基于周志明的《深入理解Java虚拟机 第三版》,第三版相比第二版更新了很多知识点。pdf版本已经上传到网盘,微信扫码关注回复【jvm】获取网盘地址和提取码

深入理解Java虚拟机(第三版)-02-HotSpot虚拟机对象探秘(1)

一、对象的创建

创建对象,通常仅仅是一个new关键字(例外:复制,反序列化),本次讨论的对象限于普通Java对象,不包括数组和Class对象等。

  • 当虚拟机遇到一条字节码new指令时。首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载解析初始化过。如果没有,必须先执行相应类的加载过程。
  • 当类加载检查通过后,虚拟机将为新生对象分配内存。对象的所需内存,在类加载完成后便可完全确认。
  • 假设Java 堆中内存时绝对规整的,所有被使用过的内存放在一边,空闲的内存放在另一边,中间放一个指针作为分界点指示器。那么内存分配就是指针指向空闲的方向,挪动一段与对象大小相等的举例。这种分配方式成为指针碰撞(Bump The Pointer)。
  • 如果Java堆内存中不规则,虚拟机就必须维护一个列表,记录哪些内存可用,哪些不可用。分配的时候在列表中找一个足够大的空间分配,然后更新列表。这种分配方式叫空闲列表(Free List)。
  • 选择哪种由Java堆是否规整决定,Java堆是否规整由所采用的的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。
  • 当使用Serial,ParNew等带有压缩整理过程的收集器,指针碰撞简单高效;
  • 当使用CMS基于清除(Sweep)算法收集器时,只能采用空闲列表来分配内存;(CMS为了能在多数情况下分配内存更快,设计了一个Linear Allocatioin Buffer的分配缓冲区,通过空闲列表拿到一大块分配缓冲区后,在它里面仍可使用指针碰撞方式分配)
  • 对象创建是非常频繁的行为,还需要考虑并发情况下,仅仅修改一个指针所指向的位置也是不安全的,例如正在给对象A分配内存,指针还未修改,对象B又使用原来的指针分配内存。解决问题有两种可选方案:
    • a、对分配内存空间的动作进行同步处理。实际上虚拟机采取CAS配上失败重试的方式保证更新操作的原子性。
    • b、把内存分配的动作按照线程划分到不同的空间中进行,每个线程在Java堆中,预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。
    • 虚拟机是否使用TLAB,可以通过-XX: +/-UseTLAB参数来设定。
  • 内存分配完成后,虚拟机将分配到的内存空间(不包括对象头)都初始化为零值。如果使用了TLAB,这个工作可以提前到TLAB分配时进行。
    这步操作保证对象的实例字段在Java代码中,可以不赋初始值就直接使用,程序可以访问到字段对应数据类型所对应的零值。
  • 接下来Java虚拟机还要对对象进行必要的设置,例如对象时哪个类的实例、如何才能找到类的元数据信息,对象的哈希码(实际上对象的HashCode会延后真正调用Object::hashCode()方法时才计算)、对象的GC分代年龄等信息。这些信息存放到对象的对象头(Object Header)

上面工作完成后,从虚拟机角度来说,一个新的对象已经产生了,但是从Java程序的视角来说,对象创建才刚刚开始,对象的构造方法(Class文件中init()方法)还未执行,所有字段都是默认的零值。new指令之后接着执行init方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全构造出来

下图是HotSpot虚拟机字节码解释器(bytecodeInterpreter.cpp)中的代码片段,用于了解HotSpot运作过程。
深入理解Java虚拟机(第三版)-02-HotSpot虚拟机对象探秘(1)
深入理解Java虚拟机(第三版)-02-HotSpot虚拟机对象探秘(1)
深入理解Java虚拟机(第三版)-02-HotSpot虚拟机对象探秘(1)
博客内容大部分来自《深入学习Java虚拟机 第三版》一书。

下一篇博客,我们会介绍对象的内存布局和对象头。
感谢大家的阅读,欢迎大家扫码关注,私信交流,转发,点在看,谢谢!