性能文章>OopMap看不懂,怎么调优哇>

OopMap看不懂,怎么调优哇原创

3436633

哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

 

手撸过JVM、内存池、垃圾回收算法、synchronized、线程池、NIO…

 

 

最近正在写三色标记算法,准备实现OopMap了,把JVM的OopMap机制认真梳理了下。为什么要梳理,而不是基于对理论的理解去写?为了尽量跟Hotspot源码保持一致。东西有点多,怕后面忘记,写几篇文章记录下。

 

国籍惯例,百度了下OopMap相关的文章,嗯,基本都是从周志明的书《深入理解Java虚拟机》摘录出来的,见怪不怪了。因为本人熟读Hotspot源码,所以本人的文章除了会把OopMap理论与GC、运行时串起来,还会加入我的独特理解,及Hotspot源码证明。

 

大概会讲这些:

1、垃圾收集器各个阶段与STW、安全点、OopMap之间的关系

2、识别数据类型的三种算法

3、GC如何找到JNI线程创建的对象

4、哪些场景会生成OopMap记录

5、生成OopMap记录的几个重要方法

6、OopMap记录如何理解

 

01

OopMap

 

一看到Oop,大家是不是条件反射:面向对象?面向对象map?好像说不通啊!

 

这里的Oop是普通对象指针的意思:ordinary object pointer。这里的map也不是指对应的数据结构是map,而是地图的意思。所以翻译过来就是普通对象指针地图,与现实很贴切。

 

何谓普通对象指针?就是指一个Java对象,在JVM中,或者Hotspot源码层面,对应一个C++实例。在Java层面,我们叫Java对象,在JVM层面,我们叫oop。

 

与之对应的就是klass,就是一个Java类在JVM中对应的C++实例。还有handle,这个是一整套机制,后面讲到GC处理JNI线程创建的对象时细讲。

 

oop-klass-handle机制,是JVM的基石,对于理解Hotspot源码,极其重要。

 

关于OopMap,很多人对它的基础理解都错了,自然看一些文章,怎么看都看不懂。

 

02

聊聊GC

 

语言学习最大的门槛是语言本身,比如C语言的指针。相对来说,高级语言如Java、Python、PHP等,语言本身难度较低,所以学起来比较容易。你觉得生活轻松,是因为有人替你承担了大多数,计算机的世界也一样。

 

高级语言学起来之所以轻松,是因为高级语言的运行环境,即虚拟机,为你承担了大多数。今天就谈谈其中之一的GC。

 

GC的目的很简单:自动回收内存。再精确点说,高效回收不被使用的对象占用的内存。为了这一目标,GC经历了串行回收到并行回收到并发回收阶段。

 

并发阶段好像已经是终点,于是开始在内存设计上做文章,经历了从一个大堆到1:1堆到1:1:8堆到region堆。好像基于region块的堆又是终点了。下一代GC将会是什么样呢?我也不知道,但我保持期待。

 

GC一般是找到还在使用的对象,打上标记,清理那些没有打标记的对象。那GC怎么找到还在使用的对象呢?分两个阶段:先找到活动对象,再基于活动对象引用链一层一层往下找。

 

活动对象在哪里:

1、Java线程栈中

2、JNI线程栈中

3、寄存器中

4、可能我没想到的情况

 

栈与寄存器都是无状态的,言外之意就是说GC时是不知道寄存器中或栈中那一串数字到底是oop还是就是数字。

 

 

那怎么办呢?JVM采用的是准确式GC,言外之意就是说能准确的知道哪些区域存储的是oop。怎么做到的呢?就是通过引入OopMap。其实就是一种空间换时间的方式,在GC来临之前,根据栈图及寄存器生成OopMap记录。

 

生成OopMap记录,必然带来额外的开销,所以不适合持续进行。就像一个商店盘点第二天需要进的货,不是卖出一件盘点一下,而是打烊前完整得盘点。程序世界也一样。那这个何时的时机是什么时候呢?就是GC之前,安全点激活,线程进入安全点的时候。

 

关于安全点,STW理解得不太好的小伙伴,可以看我另外一篇文章 传送门

 

下一个问题:GC是怎么基于根对象一层一层往下找的?言外之意是说GC是怎么知道对象哪些属性是引用?这就要说到另一个数据结构:OopMapBlock。这个本篇文章就不展开讲了,后面有机会会写文章单独讲,这里大家知道有这样一个东西的存在,做了这个事情,就可以了。学习,有的时候,需要囫囵吞枣。

 

总结一下,程序平时运行不会生成OopMap,只有在GC前才会生成OopMap。GC前会激活安全点,所有线程都会进入安全点,即STW。JVM根据平时运行形成的栈图创建OopMap记录,供GC时遍历根对象使用。这句话就是GC、STW、安全点、OopMap的完整关系。解释执行是这样运作的,编译执行还不太一样,后面讲。

 

这里有个新名词:栈图。栈图底层实现后面有空讲,大家只需要知道它是干这个事的就行:它用来标识局部变量表或操作数栈中每个slot存储的数据类型。

 

是不是有些小伙伴想到了记忆集及卡表?这里不展开讲,感兴趣的小伙伴可以看我之前写的一篇文章 传送门

 

03

准确式GC

 

前面一段好干有木有?来点不干的舒缓下情绪,因为后面还有更干的 ^_^

 

根据识别数据类型的准确度划分,有这三种算法

1、保守式GC:即不记录数据的类型,GC时全栈扫描,但是就算是全栈扫描,怎么区分是oop还是数字呢?进行边界比对,比如堆的起始位置是top,结束位置是bottom,在这中间的就是oop。很明显,这个算法实现起来很简单,但是太low了,效率也很低。

 

2、半保守式GC:根对象不记录类型,根对象派生出来的对象记录数据类型。比如根对象A的地址存储在栈中,没有标记类型,找出所有根对象需要全栈扫描,但是A对象的属性B对象,在类A的元信息中有记录,可以顺藤摸瓜找到,减少程序憨憨遍历时间。

 

3、准确式GC:不管是根对象,还是派生对象,都标记类型。JVM中引入外部数据结构OopMap+OopMapBlock实现了该算法。

 

采用准确式GC思路,本质就是说,程序平时运行多花费一点点时间记录数据,帮助GC时减少延时,是值得的。

 

到这里就把OopMap相关的理论知识讲完了。当然,Hotspot源码很庞大,关于OopMap机制的理解我也有可能犯错。对于文中的有些观点,如果你有不同的理解,欢迎找我交流,一起求真。

 

04

OopMap格式

 

经常看到的OopMap格式,大概有这些。我列出来,大家看看有木有自己见过的,下篇文章我展开讲。

 

 

 

 

你好,我是子牙。十余年技术生涯,一路披荆斩棘从小白到技术总监到大厂中间件到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核及特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。不考虑交个朋友吗?关注硬核子牙

 

点赞收藏
分类:标签:
子牙_公号硬核子牙

对编程语言的设计与实现有浓厚兴趣。聚焦Hotspot源码、Linux内核研究,硬核干货分享

请先登录,查看6条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

记一次线上RPC超时故障排查及后续GC调优思路

记一次线上RPC超时故障排查及后续GC调优思路

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

【全网首发】从源码角度分析一次诡异的类被加载问题

【全网首发】从源码角度分析一次诡异的类被加载问题

33
6