JVM源码分析之Java对象头实现

时间:2022-09-27 16:52:36
JVM源码分析之Java对象头实现

原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注

“365篇原创计划”第十一篇。

今天呢!灯塔君跟大家讲:

JVM源码分析之Java对象头实现

HotSpot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。

对象头

对象头包括两部分:Mark Word 和 类型指针。

Mark Word

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。

类型指针

类型指针指向对象的类元数据,虚拟机通过这个指针确定该对象是哪个类的实例。

markOop实现

HotSpot通过markOop类型实现Mark Word,具体实现位于markOop.hpp文件中。

由于对象需要存储的运行时数据很多,考虑到虚拟机的内存使用,markOop被设计成一个非固定的数据结构,以便在极小的空间存储尽量多的数据,根据对象的状态复用自己的存储空间,32位虚拟机的markOop实现如下:

JVM源码分析之Java对象头实现

hash: 保存对象的哈希码

age: 保存对象的分代年龄

biased_lock: 偏向锁标识位

lock: 锁状态标识位

JavaThread:* 保存持有偏向锁的线程ID

epoch: 保存偏向时间戳

markOop:中不同的锁标识位,代表着不同的锁状态:

JVM源码分析之Java对象头实现

不同的锁状态,存储着不同的数据:

JVM源码分析之Java对象头实现

markOop中提供了大量方法用于查看当前对象头的状态,以及更新对象头的数据,为synchronized锁的实现提供了基础。

下面来看看代码吧:

首先定义两个简单的类AAA和BBB

JVM源码分析之Java对象头实现

通过``javap -c AAA```查看编译之后的字节码,具体如下:

JVM源码分析之Java对象头实现

Java中的new关键字对应jvm中的new指令,定义在InterpreterRuntime类中,实现如下:

JVM源码分析之Java对象头实现

new指令的实现过程:

1、其中pool是AAA的constant pool,此时AAA的class已经加载到虚拟机中,new指令后面的#2表示BBB类全限定名的符号引用在constant pool的位置;

2、方法pool->klass_at负责返回BBB对应的klassOop对象,实现如下:

JVM源码分析之Java对象头实现

如果常量池中指定位置(#2)的数据已经是个oop类型,说明BBB的class已经被加载并解析过,则直接通过(klassOop)entry.get_oop()返回klassOop;否则表示第一次使用BBB,需要解析BBB的符号引用,并加载BBB的class类,生成对应的instanceKlass对象,并更新constant pool中对应位置的符号引用;

3、klass->check_valid_for_instantiation可以防止抽象类被实例化;

4、klass->initialize实现如下:

JVM源码分析之Java对象头实现

如果BBB的instanceKlass对象已经初始化完成,则直接返回;否则通过initialize_impl方法进行初始化,整个初始化算法分成11步,具体实现如下:

step1

JVM源码分析之Java对象头实现

通过ObjectLocker在初始化之前进行加锁,防止多个线程并发初始化。

step2

JVM源码分析之Java对象头实现

step3

JVM源码分析之Java对象头实现

如果当前instanceKlass处于being_initialized状态,且被当前线程初始化,则直接返回。

其实对于这个step的处理我有疑问,什么情况会走到这一步?经过RednaxelaFX大大提点,如下情况会执行step3:

例如A类有静态变量指向一个new B类实例,B类里又有静态变量指向new A类实例,这样外部用A时要初始化A类,初始化过程中又要触发B类初始化,B类初始化又再次触发A类初始化。

JVM源码分析之Java对象头实现

如果当前instanceKlass处于fully_initialized状态,说明已经初始化完成,则直接返回;

step5

JVM源码分析之Java对象头实现

如果当前instanceKlass处于initialization_error状态,说明初始化失败了,抛出异常。

step6

JVM源码分析之Java对象头实现

设置当前instanceKlass的状态为 being_initialized;设置初始化线程为当前线程。

JVM源码分析之Java对象头实现

如果当前instanceKlass不是接口类型,并且父类不为空,且还未初始化,则执行父类的初始化。

step8

JVM源码分析之Java对象头实现

通过thisoop->callclass_initializer方法执行静态块代码,实现如下:

JVM源码分析之Java对象头实现

this_oop->class_initializer()可以获取静态代码块入口,最终通过JavaCalls::call执行代码块逻辑,再下一层就是具体操作系统的实现了。

step9

JVM源码分析之Java对象头实现

如果初始化过程没有异常,说明instanceKlass对象已经初始完成,则设置当前instanceKlass的状态为 fully_initialized,最后通知其它线程初始化已经完成;否则执行step10 and 11。

step10 and 11

JVM源码分析之Java对象头实现

如果初始化发生异常,则设置当前instanceKlass的状态为 initialization_error,并通知其它线程初始化发生异常。

5、如果instanceKlass初始化完成,klass->allocate_instance会在堆内存创建instanceOopDesc对象,即类的实例化;.

instanceOopDesc

当在Java中new一个对象时,本质是在堆内存创建一个instanceOopDesc对象。

JVM源码分析之Java对象头实现

instanceOopDesc在实现上继承自oopDesc,其中oopDesc定义如下:

JVM源码分析之Java对象头实现

当然,这只是 oopDesc的部分实现,oopDesc包含两个数据成员:_mark 和 _metadata。

1、_mark是markOop类型对象,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致,更具体的实现可以阅读 《java对象头的HotSpot实现分析》

2、_metadata是一个联合体,其中wideKlassOop和narrowOop都是指向InstanceKlass对象的指针,wide版是普通指针,narrow版是压缩类指针(compressed Class pointer)

instanceOopDesc对象的创建过程

JVM源码分析之Java对象头实现

instanceOopDesc对象通过instanceKlass::allocate_instance进行创建,实现过程如下:

1、has_finalizer判断当前类是否包含不为空的finalize方法;

2、size_helper确定创建当前对象需要分配多大内存;

3、CollectedHeap::obj_allocate从堆中申请指定大小的内存,并创建instanceOopDesc对象,实现如下:

JVM源码分析之Java对象头实现

4、如果当前类重写了finalize方法,且非空,需要把生成的对象封装成Finalizer对象并添加到 Finalizer链表中,对象被GC时,如果是Finalizer对象,会将对象赋值到pending对象。Reference Handler线程会将pending对象push到queue中,Finalizer线程poll到对象,先删除掉Finalizer链表中对应的对象,然后再执行对象的finalize方法;

365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板

JVM源码分析之Java对象头实现的更多相关文章

  1. 源码分析:Java对象的内存分配

    Java对象的分配,根据其过程,将其分为快速分配和慢速分配两种形式,其中快速分配使用无锁的指针碰撞技术在新生代的Eden区上进行分配,而慢速分配根据堆的实现方式.GC的实现方式.代的实现方式不同而具有 ...

  2. JVM源码分析之synchronized实现

    “365篇原创计划”第十二篇.   今天呢!灯塔君跟大家讲:   JVM源码分析之synchronized实现     java内部锁synchronized的出现,为多线程的并发执行提供了一个稳定的 ...

  3. JVM源码分析之一个Java进程究竟能创建多少线程

    JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...

  4. JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...

  5. synchronized的jvm源码分析聊锁的意义

    上篇写完了ReentrantLock源码实现,从我们的角度分析设计锁,在对比大神的实现,顺道拍了一波道哥的马屁,虽然他看不到,哈哈.这一篇我们来聊一聊synchronized的源码实现,并对比reen ...

  6. JVM源码分析之SystemGC完全解读

    JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...

  7. JVM源码分析之堆外内存完全解读

    JVM源码分析之堆外内存完全解读   寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...

  8. JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)

    概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...

  9. JVM源码分析-类加载场景实例分析

    A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...

随机推荐

  1. shell浅谈之九子shell与进程处理

    转自:http://blog.csdn.net/taiyang1987912/article/details/39529291 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] ...

  2. C#利用AxImp工具在WPF中使用OCX控件

    一.注册OCX并利用工具生成dll @echo off color a ::Failed REGSVR32 /S /I "MSCOMCTL.OCX" if exist %windi ...

  3. sqlserver 查看正在执行sql

    SELECT    [session_id],  [request_id],  [cpu_time],  [start_time] AS '开始时间',  [status] AS '状态',  [co ...

  4. Scala包

    #引入包的全部成员的办法 import scala.collection._ #引入同一个包中的几个成员 import scala.collection.{A,B} #重名 要 重命名 import ...

  5. 团队SCRUM会议(第一次)

    每日Scrum:第一天 会议时间:4.30.晚八点半 会议地点:基础教学楼一楼大厅 小组成员:郭庆樑,林彦汝,张金 团队PM:张金 会议进程 • 首先我们讨论了实验第一个Sprint1要实现的功能,我 ...

  6. Linux内核中关于内存的数据结构

    物理页面 /* * Try to keep the most commonly accessed fields in single cache lines * here (16 bytes or gr ...

  7. Redis数据类型String

    set和get Set带空格的字符串值,这时候就需要使用双引号了,否则会出错 getset getset设置某个key-value,然后把设置之前的值返回来 incr,incrby 和 decr,de ...

  8. (Solved) Nexiq USB link with CAT ET: connection error 149

    User issue: I'm using a Nexiq USB link with CAT ET 2011b and I need some help with connection settin ...

  9. event based xml parser (SAX) demo

    import java.io.ByteArrayInputStream; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SA ...

  10. SSH 本地端口转发

    有时,绑定本地端口还不够,还必须指定数据传送的目标主机,从而形成点对点的"端口转发".为了区别后文的"远程端口转发",我们把这种情况称为"本地端口转发 ...