前言
自己在gitee上总结了JVM的笔记,希望对大家有帮助。
另外如果自己文章中有出错的地方,欢迎指正。
自己一个人学习真的挺无聊无趣的。希望有朋友可以一起讨论。
JVM中HotSpot虚拟机的实现细节
1. OopMap
1. OopMap是什么
一个线程意味着一个栈,一个栈由多个栈帧组成,一个栈帧对应着一个方法,一个方法中有多个OopMap,一个OopMap和方法的某一条指令进行关联。方法运行到这个指令前,需要将对象的信息记录到OopMap中。
2. 为什么要有OopMap
记录了什么位置存储的是对象引用,并且这个OopMap和方法的哪一条指令进行关联。比如在偏移量为0x003f的地方存储着一个对象引用,那么在GC在枚举根节点的时候就可以直接到这里去就行了,就没必要把所有的栈中所有变量都进行扫描一遍才能得到所有的对象引用了。
3. 怎么实现的?
在类加载完成后,HotSpot会将什么地方存储的是什么类型信息计算出来,在JIT进行及时编译的后,也会把栈中哪些位置存储的是引用计算出来。然后把这些对象引用的信息交给OopMap进行存。
4. 不大形象的比喻
这样说OopMap就像是一个驿站,线程跑到安全点休息一会,让GC收拾收拾路上的垃圾,之后再继续跑。垃圾不包括自己列举出来的,就是OopMap中的内容。其他的都可以清理掉了。
5. OopMap的数据格式
既然OopMap是一个指向普通对象引用的数结构,那么就一定是由一些字段组成的。具体来说,就包括两种数据字段对象指针数组
和关联指令
。其中对象指针数组就是它的名字,记录了普通对象的饮用。而关联指令的位置就是所谓的安全点,也就是可以让用户线程挂起,让垃圾回收线程工作的点。
{
对象指针数组 {
对象指针1,
对象指针2,
...
对象指针n.
}
关联指令偏移量(就是关联指令的位置)
}
2. 安全点
1. 什么是安全点
就是有OopMap这个数据结构的指令点,相当于程序的一个驿站。有了这个驿站之后,程序才能停下来,如果不到这个点,线程就不会停止执行,那么GC线程就无法工作,也就无法进行垃圾回收。实际上安全点就是OopMap这个数据结构中的关联指令所在的位置
。
2, 如何实现
有了安全点之后,怎么让所有的程序都到安全点呢?
两种方法:一种式抢先式的一种式主动式的。抢先式的就是,GC直接Stop The World然后,看哪些线程不在安全点上,在让他跑一会。主动式的就是线程不断“轮询”一个标志位,如果标志位为真就直接挂起自己。Hotspot虚拟机使用test %exa 0x106100这条指令作为轮询指令。就是说让这条指令作为安全点,关联一个OopMap,一旦执行了这条指令,程序就有可能挂起。为什么是有可能呢?因为0x106100这个如果可读就不会挂起,这样这个页面就成了一个标志位,在需要GC的时候,虚拟机只要将这个页面设置成不可读就行了
3. 安全点的选择
- 方法调用
- 循环跳转
- 异常跳转
就是说可以将这些语句设置成安全点,让程序执行这些命令的时候挂起自己,这些点选择不会让线程出现**总是挂起**这种情况,可以满足用户的需求和垃圾回收的需求。
3. 安全区域
- 为什么
为了防止线程由于Block或者Sleep而无法进入安全点,从而将安全点进行了“扩展”,只要这段程序中没有改变对象的引用,就认为线程在安全点上。而这段程序就是安全区域。 - 如何实现的
就是用户线程通知GC线程自己进去安全区域了,GC线程通知用户线程是否可以出安全区域。
4. 记忆集与卡表
1. 是什么
- 为了解决对象的跨代引用,垃圾回收器在新生代中设置的一种抽象的数据结构。该表用于记录从非收集区域指向收集区域的指针集合。
2. 如何实现的
- 在HotSpot 虚拟机种使用了卡表来实现这种逻辑的数据结构。卡表是一个Byte类型的数组,其中每一个索引代表着虚拟机的每一个卡页的首地址,存储内容是0或者1。
不使用bit类型的数组是因为访问和修改速度比使用byte类型的慢
- 所指向的内存被称为卡页。
索引 >> 9代表着所指向卡也的首地址
5. 写屏障
1. 是什么
卡表维护的卡页变脏了,需要修改卡表,维护卡表的这个动作在不同场景下需要用不同的手段。
- 字节码层面
- 机器码层面
这里需要插入一条后面才会有的知识
java是半解释,半编译的语言。可以理解执行字节码就是解释,执行机器语言就是编译。
在执行字节码的时候,虚拟机有足够的介入手段,但是经过编译之后,机器码就不归虚拟机管了,此时卡表的更新只能通过赋值语句进行。JVM采用的手段是在每一个跟新对象的引用的地方都插入一条更新卡表的语句。这个语句就是写屏障。
2. 伪共享
- 就是说由于缓存被更换出去,导致的写回、同步或者无效化问题,从而影响了性能。
- 解决办法就是先判断是否已经变脏了,如果没有变脏再进行操作。就是用多一次的判断来避免缓存无效化的情况。
6. 并发的可达性分析
1.采用三色分析法对并发的对象进行分析,就是说:
- 白色:代表还没扫描过
- 黑色:代表安全扫描过它以及它的子节点,并且它安全存活。
- 灰色:代表此对象安全扫描过,但是最少有一个子节点没有扫描过。
那么如果以下两种情况同时出现,那么对象就会消失:
- 插入了一条或者多条从黑色对象到白色对象的新引用。
- 删除了全部的从灰色对象到白色对象的引用。
就是这种情况,举个例子,小孩A的住过房间已经打扫过了,小孩B本来要住房间ROOM, 但是被临时取消了,就不用打扫ROOM了。但是小孩A突然住进了ROOM,并且你不知情。那么ROOM就是没有被打扫的房间,那么ROOM突然从你的能住的客房表里消失了。
2. 如何解决呢?
解决想法也很简单,破坏那两个条件的一个就行了。
- 增量更新, 破坏第一个条件
- 原始快照, 破坏第二个条件
增量更新就是说,黑色对象一旦插入对白色对象的引用之后,黑色对象就要变成灰色对象。也就是说,小孩A如果要住进ROOM,需要给你说一声,让你重新打扫一遍他住过的房间。
原始快照,当灰色对象要删除对白色对象的引用时,就把这个删除的引用记录下来,在扫描结束后,在扫描一次这个应用。就是小孩A虽然说自己不住了,但是这个房子还要留着,等打扫完之后,再重新去看一下到底有没有被住过。
2. 几句废话
博客到此就结束了,但是虚拟机的学习才刚刚开始。这篇文章是自己总结笔记以来写的比较认真的文章了。虽然刚开始,还有很多问题,还有很多自己不满意的地方,比如缓存的机制自己没写进去,比如标题安排的各种问题,让文章显得不是特别美观,再比如写屏障因为自己理解的不深刻,难以举出来生动的例子帮忙理解。不过一切都是刚刚开始,以后争取向高质量的文章迈进吧。最后给自己提点建议吧。
-
希望以后能够把问题说的生动一些。
-
希望以后能够安排好排版
-
希望以后能有更加实际的例子,更加丰富的图文和更加精炼的代码。
-
最后还是自己的JVM总结
3. 参考文章
JVM 之 OopMap 和 RememberedSet
OopMap的数据格式
伪共享
AOP与环形通知
cache 与主存的映射
《深入理解JVM》第三章