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实现如下:
hash: 保存对象的哈希码
age: 保存对象的分代年龄
biased_lock: 偏向锁标识位
lock: 锁状态标识位
JavaThread:* 保存持有偏向锁的线程ID
epoch: 保存偏向时间戳
markOop:中不同的锁标识位,代表着不同的锁状态:
不同的锁状态,存储着不同的数据:
markOop中提供了大量方法用于查看当前对象头的状态,以及更新对象头的数据,为synchronized锁的实现提供了基础。
下面来看看代码吧:
首先定义两个简单的类AAA和BBB
通过``javap -c AAA```查看编译之后的字节码,具体如下:
Java中的new关键字对应jvm中的new指令,定义在InterpreterRuntime类中,实现如下:
new指令的实现过程:
1、其中pool是AAA的constant pool,此时AAA的class已经加载到虚拟机中,new指令后面的#2表示BBB类全限定名的符号引用在constant pool的位置;
2、方法pool->klass_at负责返回BBB对应的klassOop对象,实现如下:
如果常量池中指定位置(#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实现如下:
如果BBB的instanceKlass对象已经初始化完成,则直接返回;否则通过initialize_impl方法进行初始化,整个初始化算法分成11步,具体实现如下:
step1
通过ObjectLocker在初始化之前进行加锁,防止多个线程并发初始化。
step2
step3
如果当前instanceKlass处于being_initialized状态,且被当前线程初始化,则直接返回。
其实对于这个step的处理我有疑问,什么情况会走到这一步?经过RednaxelaFX大大提点,如下情况会执行step3:
例如A类有静态变量指向一个new B类实例,B类里又有静态变量指向new A类实例,这样外部用A时要初始化A类,初始化过程中又要触发B类初始化,B类初始化又再次触发A类初始化。
如果当前instanceKlass处于fully_initialized状态,说明已经初始化完成,则直接返回;
step5
如果当前instanceKlass处于initialization_error状态,说明初始化失败了,抛出异常。
step6
设置当前instanceKlass的状态为 being_initialized;设置初始化线程为当前线程。
如果当前instanceKlass不是接口类型,并且父类不为空,且还未初始化,则执行父类的初始化。
step8
通过thisoop->callclass_initializer方法执行静态块代码,实现如下:
this_oop->class_initializer()可以获取静态代码块入口,最终通过JavaCalls::call执行代码块逻辑,再下一层就是具体操作系统的实现了。
step9
如果初始化过程没有异常,说明instanceKlass对象已经初始完成,则设置当前instanceKlass的状态为 fully_initialized,最后通知其它线程初始化已经完成;否则执行step10 and 11。
step10 and 11
如果初始化发生异常,则设置当前instanceKlass的状态为 initialization_error,并通知其它线程初始化发生异常。
5、如果instanceKlass初始化完成,klass->allocate_instance会在堆内存创建instanceOopDesc对象,即类的实例化;.
instanceOopDesc
当在Java中new一个对象时,本质是在堆内存创建一个instanceOopDesc对象。
instanceOopDesc在实现上继承自oopDesc,其中oopDesc定义如下:
当然,这只是 oopDesc的部分实现,oopDesc包含两个数据成员:_mark 和 _metadata。
1、_mark是markOop类型对象,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致,更具体的实现可以阅读 《java对象头的HotSpot实现分析》
2、_metadata是一个联合体,其中wideKlassOop和narrowOop都是指向InstanceKlass对象的指针,wide版是普通指针,narrow版是压缩类指针(compressed Class pointer)
instanceOopDesc对象的创建过程
instanceOopDesc对象通过instanceKlass::allocate_instance进行创建,实现过程如下:
1、has_finalizer判断当前类是否包含不为空的finalize方法;
2、size_helper确定创建当前对象需要分配多大内存;
3、CollectedHeap::obj_allocate从堆中申请指定大小的内存,并创建instanceOopDesc对象,实现如下:
4、如果当前类重写了finalize方法,且非空,需要把生成的对象封装成Finalizer对象并添加到 Finalizer链表中,对象被GC时,如果是Finalizer对象,会将对象赋值到pending对象。Reference Handler线程会将pending对象push到queue中,Finalizer线程poll到对象,先删除掉Finalizer链表中对应的对象,然后再执行对象的finalize方法;
365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板
JVM源码分析之Java对象头实现的更多相关文章
- 源码分析:Java对象的内存分配
Java对象的分配,根据其过程,将其分为快速分配和慢速分配两种形式,其中快速分配使用无锁的指针碰撞技术在新生代的Eden区上进行分配,而慢速分配根据堆的实现方式.GC的实现方式.代的实现方式不同而具有 ...
- JVM源码分析之synchronized实现
“365篇原创计划”第十二篇. 今天呢!灯塔君跟大家讲: JVM源码分析之synchronized实现 java内部锁synchronized的出现,为多线程的并发执行提供了一个稳定的 ...
- JVM源码分析之一个Java进程究竟能创建多少线程
JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...
- JVM源码分析-JVM源码编译与调试
要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...
- synchronized的jvm源码分析聊锁的意义
上篇写完了ReentrantLock源码实现,从我们的角度分析设计锁,在对比大神的实现,顺道拍了一波道哥的马屁,虽然他看不到,哈哈.这一篇我们来聊一聊synchronized的源码实现,并对比reen ...
- JVM源码分析之SystemGC完全解读
JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...
- JVM源码分析之堆外内存完全解读
JVM源码分析之堆外内存完全解读 寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...
- JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)
概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...
- JVM源码分析-类加载场景实例分析
A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...
随机推荐
- Java实现 蓝桥杯VIP 算法提高 栅格打印问题
算法提高 栅格打印问题 时间限制:1.0s 内存限制:512.0MB 问题描述 编写一个程序,输入两个整数,作为栅格的高度和宽度,然后用"+"."-"和&quo ...
- java实现第五届蓝桥杯猜年龄
猜年龄 题目描述 小明带两个妹妹参加元宵灯会.别人问她们多大了,她们调皮地说:"我们俩的年龄之积是年龄之和的6倍".小明又补充说:"她们可不是双胞胎,年龄差肯定也不超过8 ...
- Java基础(十一)
一.连接到服务器 telnet是一种用于网络编程的非常强大的测试工具,你可以在命令shell中输入telnet来启动它. 二.实现服务器 服务器循环体: 1.通过输入数据流从客户端接收一个命令. 2. ...
- Zabbix 邮箱告警(Python脚本)
Python 脚本内容 #!/bin/env python #coding:utf- import smtplib from email.mime.text import MIMEText from ...
- 树莓派学习--安装GPIO Zero
GPIO ZERO 在Raspbian的映像内默认安装,对于其他系统 先更新存储库列表: sudo apt update 对于python3: sudo apt install python3-gpi ...
- JS 姓氏,区域,消息组成的随机内容定时随机展示
var surname_g = "\u8D75\u94B1\u5B59\u674E\u5468\u5434\u90D1\u738B\u51AF\u9648\u891A\u536B\u848B ...
- mysql域名解析引起的远程访问过慢?
MYSQL远程连接速度慢的解决方法 PHP远程连接MYSQL速度慢,有时远程连接到MYSQL用时4-20秒不等,本地连接MYSQL正常,出现这种问题的主要原因是, 默认安装的MYSQL开启了DNS的反 ...
- Excel 中将大于511的十进制数转换成二进制
If number < -512 or if number > 511, DEC2BIN returns the #NUM! error value. 所以不能直接使用DEC2BIN函数. ...
- @bzoj - 5104@ Fib数列
目录 @description@ @solution@ @accepted code@ @details@ @description@ Fib数列为1,1,2,3,5,8... 求在Mod10^9+9 ...
- postman切换环境
原文链接:https://www.cnblogs.com/nicole-zhang/p/11498384.html 通常会有多个测试环境,针对同一个接口来说,可能只是域名有变化,此时可以添加postm ...