11

了解一下,Android 10中的ART虚拟机(9)-完结

 3 years ago
source link: https://blog.csdn.net/innost/article/details/107602892
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

了解一下,Android 10中的ART虚拟机(9)-完结

继续从读书笔记的角度来系统的学习《Advanced Design and Implementation of Virtual Machines》。这本书是英文编写,全部、认真、深入的读下来非常考验人。我之前也只是读了60%,而且越到后面越没有耐心。想了想,JVM在系统层面上要达到一定水准,可能还是得精读一到两本这样的书籍。记读书笔记是我学习知识和技能的一种比较好的方式,伴随我至少20多年了。暂且给这次的读书笔记取名“关于VM的理论”。

本篇介绍最后两个部分:

format,png

本文是我们对ADIVM一书阅读笔记的结束。今天这篇文章会讲得稍显粗糙,毕竟,这属于高级部分——优化嘛....有些地方我只能整理个大概,让你知道作者在说什么(如果不整理的话,基本属于不知所云,完全搞不清状况的那种)。

GC基础知识回顾

先回顾下GC基础知识,也是我在《深入理解Android JVM ART》一书第十四章里的摘抄。

从大道理上说,一共有四种基础GC方法。

  • Mark Sweep

  • Copying Collection

  • Mark Compact

  • Reference Counting

下面是除Reference Counting之外的三种GC方法的示意图。

format,png

Mark Sweep的原理如上。大致是找到存活对象,然后把垃圾对象就地清理掉。这种方法理解起来相对简单,也没有什么附加操作。但实际使用时,会造成内存碎片。

format,png

Copying collection就是把存活对象拷贝到一个空闲区域。拷贝的时候可以重新排位置,让大家挤在一起。这样,内存碎片的问题就解决了。但这个拷贝肯定有开销,而且还存在拷贝前/后对象被引用的问题。另外,内存空间还要事先留一块做空闲区域,有点浪费。

format,png

Mark Compact其实和Copying差不多。只不过它不留单独一块空闲区域。而是移动....所谓的Compact(压缩,其实就是移动)

以上是三种基础GC的示意。接下来我们看看书中是怎么优化GC的。

GC优化之提高吞吐量(Throughput)

作者首先谈如何提高GC的吞吐量。这一章一上来就是一堆公式。我觉得主要难在如何定义和计算吞吐量上。我们先看看作者关心的是什么。

format,png

在讨论GC吞吐量时,作者的第一个优化设计考虑点是想讨论Partial Heap和Full Heap回收的时机选择问题。也就是minor/major GC如何搭配。注意,这基于了一个大前提,就是对Heap区域进行了划分。上面图中左下角是Heap的划分。右边是分类。

基于这么一个目的,整出了几个数学公式。下面是对原文内容的高度提炼。我感觉看不看都可以...BTW,我翻了下美亚上对这本书的评价,其中有一条说到,本书后面有一大堆引用材料。但是在正文里却一丁点没提到某个内容来自哪个文献。说实话,我对下面的吞吐量计算感觉模模糊糊的,但是又不知道是作者自己想出来的还是参考了哪些文献....

format,png

作者首先定义了吞吐量的计算公式,即APP运行期内总的回收内存大小/总的回收耗时。但这个太难计算,所以又定义了一个周期,即从一次major gc结束开始,到下一次major gc结束时结束。这个周期内包含一次major gc和若干个minor gc。假设每次minor gc的耗时差不多(都是Tminor),一次major gc的耗时差不多(都是Tmajor)。巴拉巴拉....

下面就到头晕的地方了....

format,png

上面的公式里其实做了不少假设。比如,minor gc后,应用大概都会分配dS大小的非垃圾内存.....但我感觉还不止这些假设,所以这一章其实难度挺大(现在理解美亚那个评论所体现的无奈了吧)。

format,png

最后,作者得到一个吞吐量计算公式。在Fmax/dS/Tmin/Tmax固定的情况下,解了一个微分方程。然后,下面又假设Fmin=16MB,做了一组吞吐量测试。这个逻辑确实跳跃太大(实在没明白这个图是怎么画出来的).....

format,png

上图左下角的优化前提条件以及最终的结论(早一点开始做Major GC)。

接着,对提高吞吐量的第二个设计考虑是使用分代GC还是不分代GC

format,png

以上是一些关键知识提炼。最终的结论如下:

format,png

按作者的测试结果,分代GC的吞吐量一开始比不分代GC低,但后面又高了。所以,作者提供的优化手段就是开始做不分代GC,吞吐量高。到某个点后,改做分代GC.....优化的思路就是这么简单粗暴。当然,具体怎么做到还是挺考验的。但目标确实就是这么直接..

提高吞吐量的最后一个设计考虑如下,这个主要从提高运行速度来看的。

format,png

GC优化之提高Scalability

GC的另一个优化是如何利用多核优势,提高吞吐量。这一章更枯燥,建议了解要做什么先..

format,png

上图中解释了concurrent gc和parallel gc的区别。concurrent gc强调的是mutator和collector的关系,并行工作。而parallel gc强调的是有多个collector的并行工作。

这一章讨论的内容见1、2、3的介绍。再次强调,本书的一个非常大的特点是很多同级的内容并不是并列关系。比如上图中所说的1和2。Object Traversal包含了Object Marking。另外,请注意Mark-Stack这个数据结构。这也是ART一书中GC部分常见的词。还有,书中说,根对象枚举由于需要暂停mutator的运行,Scalability对这个场景没有什么意义…

Traversal其实就是为了mark。但单独讲mark,只是mark这块还可以做一些别的优化设计…

针对多核,优化设计考虑点之一就是Parallel object traversal。由于被扫到的对象都要加到MarkStack里,这就变成一个典型的多写/多读的生产者/消费者模型。见下图解释:

format,png

当然,如果要加上负载均衡等处理,情况又会复杂多了。总之,作者在这一章里提到的三种优化方法都是更好解决多写/多读问题的。具体细节不讨论。有人会觉咱这篇文章里可能什么也没说。其实,你要是没有机会看到这篇文章的话,我有90%的几率预测你可能看不懂原文(或者是不知道原文在讨论什么)format,png

接下来讨论的Parallel Object Marking。

format,png

正如我前面所说,上面两页图的内容在原文中是并列的标题。但显然Object Marking讨论的只是Traversal的一个小点....

最后,这一章还讨论一个优化设计点——Parallel Compaction。

format,png

先留着吧。等你看到原文这部分的时候再来回顾....

GC优化之提高Responsiveness

提高响应速度,这恐怕是很多键盘侠都能出来说道几句的地方。这个也是成熟技术了,我感觉主要手段还是concurrent....

format,png

优化设计考虑点之一——Concurrent Tracing。关键内容如下:

format,png

三个基本保证很重要。作者讨论相关GC方法时,最终要归结到满足这三个条件。

  • 不能漏掉一个存活对象

  • 可以保留一些垃圾对象,但最终要能回收它们

  • Tracing要能结束,不能陷入死胡同出不来。

Concurrent Tracing的整体情况如上图所示。首先,要明确Root根对象枚举完之后,才有concurrent之说。针对图中3中最后提到的问题,设计了下面三种办法。

format,png

本文只结束Snapshot-At-The-Beginning法。借助Write Barrier,当对象的成员变量被修改的时候,我们记住。注意看上面的示意图。

  • A.f1,f2,f3分别指向B、C、D三个对象。

  • 此时,A.f1赋值给了a,随后A.f1指向X。那么,A.f1原来指向的B对象就没有被记住。也不可能被找到。因为A被标记过了,不会再次被标记。

所以,借助Write-Barrier,我们主动记住B。相当于B也是被记住的对象。B需要被记住的原因是它可能是一个垃圾对象,也可能不是一个垃圾对象。不能现在就把它看做是垃圾对象(否则一旦被回收就会出问题。要注意的点是,mutator和collector此时是同时工作的)。SATB(Snapshot-At-The-Beginning)法就是这么个意思。里边还有一些变种方法。

接下来的优化设计考虑点是concurrent root-set enumeration。这个是有点难度。对这个优化点的真实含义需要仔细阅读原文。即它讨论的是多个mutator之间如何concurrent进行root-set枚举。

format,png

ART里,貌似没有使用“这么高级”(也可能是没有成熟实现)的办法。ART里线程栈中根对象的Visit属于NonCurrentRoot,是要暂停所有mutator后才能访问的....

Concurrent Moving Colleciton

这一章的安排也是中了我上面说内容并排的问题。前面三章从Throughput、Scalability、Responsiveness三个方面讲如何优化GC。这一章突然就是某个具体的GC方法...

ART里也有地方用到了concurrent moving gc法。注意下面的关键步骤。

format,png

CMC(Concurrent Moving Collection的缩写)优化设计考虑是一个出发点。就是mutator是否看到两个Obj A(一个是原Obj A,在From-Space里,一个是拷贝过去的Obj A',在To-Space里)。如果只看到To-Space里的Obj A,这个设计就叫To-Space Invariant。本篇主要介绍这个设计

format,png

注意2.a的处理。Rootset枚举完后,需要先把Root Set里的根对象移动到To-Space里。然后才恢复mutator的运行。

而关于2.b的处理,则借助了Read Barrier。见下图。

format,png

ART代码里会经常出现kUseBakerReadBarrier的常量。这个就是上图示意的Read-Barrier,其实严格来说是Load Barrier。因为不光Read,Write的时候也要做一些工作。这个方法最早是Baker提出来的.....

以上,就是和GC有关的优化部分。难度相当大。而且,原文也没有和参考文献做一个比较明确的关联,所以读起来会比较痛苦。我建议还是了解目的,具体怎么弄的,倒是可以结合ART的源码进行细致研究。

Lock实现和基于硬件的内存事务

本书最后两章分别讨论了对Lock的优化以及对基于硬件的内存事务技术在JVM上可能带来的帮助。

先看对Lock的介绍

format,png

说实话,我觉得不如直接看ART一书的第12.3节。ART的Lock综合了上图中提到的几种技术。上面的讨论其实更多像是一个回顾(当然,你要是不了解ART做法的话,不会有这种感觉)。这几项技术倒也不复杂,建议直接看我书(结合代码)算了。比这里讨论的强(我认为作者是在回顾这些优化技术,而不是提出什么新的东西)

本书最后一章介绍了基于硬件的事务内存技术在JVM实现里可能的用处。我不想讲太多,这个看下图的左下角一部分概要介绍即可。

format,png

JVM读书笔记总结

这本书总体上来说内容相当丰富。JVM本身是一门工程。工程的话,经验、某些领域优化的积累会相对较多,没有太多条条框框的理论(GC其实更多是一种优化)。在阅读ART源码的时候,注定会碰到本书提到的内容。所以,这本书应该是一本集成汇总类的书籍(其参考的各种文献材料也是后续做深入研究的宝库)。

最后的最后

  • 我期望的结果不是朋友们从我的书、文章、博客后学会了什么知识,干成了什么,而应该是说,神农,我可是踩在你的肩膀上的喔。

  • 关于学习方面的问题,我已经讨论完了。后面这个公众号将对一些基础的技术,新技术做一些学习和分享。也欢迎你的投稿。不过,正如我在公众号“联系方式”里说的那样——郑渊洁在童话大王《智齿》里有一句话令我印象深刻,大意是“我有权保持沉默,但你说的每一句话都可能成为我灵感的源泉”。所以,影响不是单向的,很可能我从你那学到的东西更多。

format,png

神农和朋友们的杂文集

长按识别二维码关注我们


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK