现在android上的Dalvik是用哪个垃圾回收算法与实现

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&如何评价《垃圾回收的算法与实现》及其作者中村成洋?
R大力荐的图书《垃圾回收的算法与实现》的中文版马上就要面世了,相对来说日本的开发者和计算机图书在国内并未被很多人了解,所以请大家来科普一下。对于书的内容,作者的成就甚至八卦都可以聊聊
按投票排序
嗯这本书的中文版快出了,真是欣慰!原版刚出的时候我就赶紧在日本Amazon上买了。后来達人出版会发行了,我还又买了一次电子版以表支持。===============================================关于中村成洋中村成洋大大的Twitter账号是(日文)和(英文)。个人网站(包括博客)在。他的工作中我所关注的部分是他对CRuby的GC的改进,例如CRuby 1.9的lazy sweep、CRuby 2.0的bitmap marking等。感觉他是个平易近人的程序员,跟他讨论问题时氛围总是轻松活泼的。他好学且勤于实践。跟许多其他日本程序员一样,他乐于并擅长阅读开源代码,并且记下学习笔记。《垃圾回收的算法与实现》书中,源码剖析(“实现”)的部分主要是中村大大写的。原本这本书还计划包含对G1 GC的源码剖析,但时间和篇幅实在是安排不下,他就另外通过達人出版会出版了G1 GC源码剖析的上下两册书:算法篇:这本书基本上是中村大大围绕G1 GC的原始论文做的说明。论文没读懂的同学,这本书是你们的福音 &_& 论文在此:实现篇:这本书是众筹资金来赞助中村大大写的。而写出来的成果也以CC BY-SA许可证发布在Github上:===============================================关于这本书题主说的这本书,原版是,中文版由图灵引进,译者是丁灵,责任编辑是下面回答的
:。我在自己的几个书单里都有放这本书:从入门到进阶阅读学习GC知识,这本可以作为第一本书;后面接上老的也行,或者跳过这本老的直接到亦可。这本书覆盖了老书的大部分基本算法和变种介绍,如果不是特别想考古或者说想把GC领域的历史都挖出来看看的话,就可以跳过这本老书了。这本书所覆盖的内容,看目录就一目了然了:序章アルゴリズム編第1章 GCを学ぶ前に第2章 マークスイープGC(Mark Sweep GC)第3章 参照カウント(Reference Counting)第4章 コピーGC(Copying GC)第5章 マークコンパクトGC(Mark Compact GC)第6章 保守的GC(Conservative GC)第7章 世代別GC(Generational GC)第8章 インクリメンタルGC(Incremental GC)実装編第9章 PythonのGC第10章 DalvikVMのGC第11章 RubiniusのGC第12章 V8のGC補遺中文版里,原作者之一的相川光还专门在算法篇新加了一章讲RC Immix的,一种结合tracing与引用计数的新算法。作者太好人了 &_&其中实现篇里介绍的GC,具体是:Python 3.0.1。其引用计数以及备份的mark-sweep GC(所谓Cycle GC)都有介绍。在讲解GC的概念与思路上,这章比写得好。Dalvik VM Android 1.5r2 (Cupcake)版。重点是:分块的GC堆、mark-sweep GC、bitmap marking。当然,现在看来这是比较老的Dalvik VM,后来新加的并发GC(Concurrent Mark-Swep)、copying GC都没有介绍。Rubinius,基本上反映了1.0.0版的状况。Rubinius的GC是分代式的,young gen为copying GC,old gen为Immix GC。本书的源码剖析只着重介绍了其young gen的实现,以及准确式GC的实现方式。V8 1.3.13.5。重点是:GC堆的划分、tagged pointer、准确式GC的实现、old gen的mark-compact。这从现在看来也很老了。后来的V8的GC发展得相当复杂,不过要追根溯源还是能找到跟早期版本相似的基本思路。这样,4个实现就分别覆盖了引用计数、mark-sweep、copying与mark-compact算法在实际VM中的实现与应用。理论结合了实际。我特别推荐《垃圾回收的算法与实现》求出版社引进中文版,是因为它非常适合用于入门。它既系统的介绍了GC的经典基础算法(及其若干变种),又贴近现实剖析了一些实用系统中的GC实现,在抽象与具体,理论与实现之间找到了一个不错的平衡。有不少着眼入门水平的书在这种平衡点上就拿捏得不好:要么通篇理论,过于抽象,难以结合代码形象的理解,读完书还是实现不出东西来;要么过于注重单一特定的简单实现,缺乏大局观,难以让读者有效的萃取出重点思路举一反三泛化到理解整个领域。而且这本书通篇的记述都配合着大量形象的插图,非常有助于读者理解其相关文字描述。要说读得不过瘾的地方,那就是算法篇中对GC知识的系统性把握还是不如《The Garbage Collection Handbook》;而实现篇中剖析的对象从现在的角度看版本偏老,而且有些系统实现得复杂的地方被略过了(例如分析Rubinius的GC时只分析了young gen的copying GC,而没有分析old gen的Immix GC),读完真恨不得给它加几个小章节…但即便如此,这本书既授人以鱼,也授人以渔,读者了解了分析代码的思路后,结合算法篇里对领域的介绍,要去阅读其它GC实现的源码也就会有头绪了。所以那些缺失的源码剖析,就算是留给读者的习题吧(&_&
这本书的责编路过(掩面)~这本书是R大极力推荐,我们才小心翼翼地拿下。不想介绍太多,否则有广告嫌疑哈哈。补充一点:因为书的部分内容有点过时,所以作者相川光为中文版补写了一章(即算法篇的特别章 RC Immix算法),把新技术补充进去。所以中文版的目录会是这样的:序章【算法篇】第1章 学习GC之前第2章 GC标记-清除算法(Mark Sweep GC)第3章 引用计数法(Reference Counting)第4章 GC复制算法(Copying GC)第5章 GC标记-压缩算法(Mark Compact GC)第6章 保守式GC(Conservative GC)第7章 分代垃圾回收(Generational GC)第8章 增量式垃圾回收(Incremental GC)特别章 RC Immix算法【实现篇】第9章 Python的垃圾回收第10章 DalvikVM的垃圾回收第11章 Rubinius的垃圾回收第12章 V8的垃圾回收补遗 Python、Java、Ruby、JavaScript简单入门
这本书的日文版在这里 -ガベージコレクションのアルゴリズムと実装日本网友对这本书还是很好评的作者的介绍也能看到,感觉他的经历还是很有意思,目前是CRuby的一个committer。之前在ruby-china上看到有关GC的一个演讲,竟然就是中村成洋(nari)的作品,链接在这里:在视频里面有很多亮点,刚才又看了一下 - 自称是GC entertainer- 还有什么中村是Ruby界最强的族(什么鬼)中村成洋先生主要负责这本书的实现部分。
更关心谁翻的,通顺不。。。
看起来有点意思。希望到中国也能有电子版。不想抱着纸书跑来跑去了。
等书出了,看出版社的文宣吧
已有帐号?
无法登录?
社交帐号登录Android Art虚拟机垃圾回收是否可以通过调整属性值调优?如果可以,How?
是否可以通过调整属性heapmaxfree、heapminfree、heaptargetutilization等的取值进行调优
不请自来学生党一枚,仅供参考。================================================================首先我的结论:可以通过调整属性值heapmaxfree对Android ART虚拟机垃圾回收进行部分性能的调优,即增大heapmaxfree能降低GC的触发频率,但会稍微增加应用的内存占用,对GC执行时间、GC暂停时间、GC吞吐量等关键GC性能指标的影响很小。一、ART虚拟机垃圾回收属性值在build.prop中一般会设置如下几个与Android ART虚拟机垃圾回收相关的属性值://以下为联想P1手机的数据
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=384m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
前三个值是控制Java堆的总大小的,包括堆的起始大小、堆的增长上限等,与手机的硬件配置相关,一般手机的内存越大值越大。堆利用率heaptargetutilization、堆最小空闲内存heapminfree和堆最大空闲内存heapmaxfree这三个值的设置则对垃圾回收(GC)的某些性能有影响,下面会详细分析。二、ART GC何时用到这三个值首先从ART的GC流程中找出用到这三个值的地方。ART分配对象失败或者已使用内存超过某个设定的阈值就会触发垃圾回收(GC),GC时调用的接口函数是CollectGarbageInternal,此方法的实现如下所示:collector::GcType Heap::CollectGarbageInternal(…) {
collector-&Run(gc_cause, clear_soft_references || runtime-&IsZygote());//执行GC
RequestHeapTrim();//裁剪堆
reference_processor_.EnqueueClearedReferences(self);// 将被回收了的引用对象添加到各自关联的队列中
GrowForUtilization(collector);//调整堆的大小
此方法首先会根据选定的垃圾回收算法执行一次垃圾回收,通常的设置是前台垃圾回收(Foreground GC)执行的是并发标记清除算法(CMS),后台垃圾回收(Background
GC)执行的是Compacting
GC,在GC执行完成之后,就进入了垃圾回收收尾阶段,此阶段主要完成如下三件事:(1)RequestHeapTrim()对堆进行裁剪GC回收垃圾完成之后需要决定是否要对堆进行裁剪,也就是将空闲内存临时归还给操作系统。此操作会对堆进行一次裁剪操作,而裁剪一个堆空间时会先锁住这个堆然后才能执行扫描操作,这会造成一点卡顿。谷歌目前对于何时进行堆裁剪的设定如下:如果5秒内没有任何堆裁剪发生过,那么此次GC之后就得进行一次堆裁剪。这个简单的设定防止了堆裁剪的过于频繁。(2)EnqueueClearedReferences()处理被回收了的引用对象调用函数EnqueueClearedReferences将目标对象已经被回收了的引用对象添加到各自关联的队列中去,以便应用程序可以知道它们的目标对象已经被回收了。(3)GrowForUtilization()调整堆的大小调用函数GrowForUtilization根据预先设置的堆目标利率以及最小和最大空闲内存数调整预留空闲内存大小,同时也会重新设置下次的GC策略以及并发垃圾回收的触发阈值。 综上,堆利用率heaptargetutilization、堆最小空闲内存heapminfree和堆最大空闲内存heapmaxfree这三个值在GrowForUtilization()中使用,用来调整GC后堆的大小。三、这三个属性值的作用GC触发后,垃圾回收器回收了应用不再使用的垃圾对象,这样应用的空闲内存就可能很大或者由于回收垃圾不够多导致空闲内存还是很小。如果此空闲内存很大,Android系统出于提高内存利用率的考虑是不会把这么大一块内存都给应用程序的,它会根据应用预先设定的堆利用率(heaptargetutilization)、最大和最小空闲内存数(heapmaxfree、heapminfree)等参数来调整此空闲内存的大小;如果此空闲内存很小,那么势必此空闲内存将很快分配光,下次GC会来的很快,所以遇到这种情况,ART会扩大此空闲内存的大小。堆利用率(heaptargetutilization)、最大空闲内存(heapmaxfree)和最小空闲内存(heapminfree)在代码里的变量名为:utilization,max_free_,min_free_。我们知道 ART中有三种GC策略:i)Sticky GC:分代GC,只回收上次GC后分配的对象ii)Partial GC:局部GC,不回收zygote堆iii)Full GC:全局GC当前GC使用哪种GC策略ART有一套判断的标准,总的来说,Sticky GC可能性最大,Full GC可能性最小。 对于不同的GC策略,预留空闲内存有不同的计算方法。(1)如果GC策略是局部垃圾回收(Partial
GC)或者全局垃圾回收(Full
GC),那么预留空闲内存的计算方法如下所示:const float multiplier = HeapGrowthMultiplier();//获取堆扩展系数
intptr_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated;//由堆利用率算出所需空闲内存
target_size = bytes_allocated + delta * multiplier;//由堆利用率决定的最终堆大小
target_size = std::min(target_size, bytes_allocated +
static_cast&uint64_t&(max_free_ * multiplier));//考虑最大空闲内存后的最终堆大小
target_size = std::max(target_size, bytes_allocated +
static_cast&uint64_t&(min_free_ * multiplier));//考虑最小空闲内存后的最终堆大小
此计算方法首先会获取一个堆扩展系数,它是根据前后台GC来区分的,一般前台GC此系数是2,也就是把空闲内存扩大2倍,后台GC此系数是1。因为对于前台GC来说,此时应用程序是运行在前台的,如果堆扩展较小,那么空闲内存就会更快的用光,也就是说下次GC来的更快,而GC会给应用造成短时间的暂停,影响应用的性能,所以说为了使前台应用有更好的性能,ART运行时会给前台应用更多的堆空闲空间。
然后由堆利用率(utilization)算出理论上所需的空闲内存,堆利用率按照谷歌的推荐一般设为0.75。此时算出来的堆大小还不是最终结果,还需考虑另外两个限制值:最小空闲内存(min_free_)和最大空闲内存(max_free_)。也就是需要把预留空闲内存控制在两倍的最小空闲内存和两倍的最大空闲内存之间。这样获得的target_size才是堆的最终大小,也就是已分配对象的大小和预留空闲内存之和。对于Partial GC和Full GC,预留空闲内存的大小和已分配对象的大小的关系如下:------------------------------ 图 1------------------------------------------------------------ 图 1------------------------------更直观点画个图:------------------------------图 2
------------------------------ (2) 如果GC策略是分代垃圾回收(Sticky
GC),也就是只回收上次GC后分配的对象,那么预留空闲内存的大小按如下方式计算:if (bytes_allocated + max_free_ & max_allowed_footprint_) {
target_size = bytes_allocated + max_free_;
target_size = std::max(bytes_allocated, static_cast&uint64_t&(max_allowed_footprint_));
max_allowed_footprint_是堆增长的上限值,上述计算方法就是在堆空间允许的范围内,尽量使预留空闲内存为最大空闲内存(max_free_),而与已使用内存的多少无关,如下图:------------------------------ 图 3------------------------------------------------------------ 图 3------------------------------综上,堆利用率heaptargetutilization、堆最小空闲内存heapminfree和堆最大空闲内存heapmaxfree这三个值是用来调整GC后堆的空闲内存大小的,并且对于不同的GC策略有不同的调整方法,如图2、3所示,从而使GC后堆的空闲内存不至于太大或者太小,太大则浪费内存,太小则会导致GC触发次数增多。四、通过属性值对ART GC调优从图2、3看出,(a)当前使用Sticky GC则预留空闲内存为heapmaxfree;(b)当前使用非Sticky GC则预留空闲内存在2*heapminfree到2*heapmaxfree之间变动,utilization决定转折点,即(ub/(1-u),2b)和(ua/(1-u),2a),一般utilization都默认设为0.75,所以当应用已使用内存超过3a即3*heapmaxfree后,预留空闲内存恒定为3*heapmaxfree从上面的分析可以看出,属性值heapmaxfree、heapminfree、heaptargetutilization对ART GC的性能影响如下,i)heapmaxfree:当应用使用内存超过3*heapmaxfree后,预留空闲内存恒为2*heapmaxfree,所以heapmaxfree越小,2*heapmaxfree大小的空闲内存就会越快用光,GC就会触发的越频繁。增大heapmaxfree可以使应用的GC频率降低,但会使应用的内存占用变大。ii)heapminfree:当应用使用内存小于3*heapminfree时,预留空闲内存设为2*heapminfree,一般来说应用在启动时占用内存才会这么小。iii)heaptargetutilization:影响不大,一般都默认设为0.75。综上,三个属性值heapmaxfree、heapminfree、heaptargetutilization中,相比较而言,heapmaxfree对GC调优效果最好,能影响GC触发的频率,对GC执行时间、GC暂停时间、GC吞吐量等关键GC性能指标的影响很小。对于内存2-3GB的手机,heapmaxfree一般设为8MB,如果GC触发比较频繁,则可以把heapmaxfree设的大一点。
已有帐号?
无法登录?
社交帐号登录2107人阅读
Android(47)
Java(10)
尊重个人劳动成果,转载请注明出处:
文中很多内容说到了JVM,我想通过研究学习JVM来达到认识DVM的目的。为了严谨,查询了一下
JVM和DVM的不同点
1、Dalvik 和标准 Java 虚拟机(JVM)的首要差别
Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。
2、Dalvik 和 Java 字节码的区别
Dalvik执行.dex格式的字节码,而JVM执行.class格式的字节码。android程序编译完之后生产.class文件,还有通过aapt工具生成的R.class等,然后dx工具会把.class文件处理成.dex文件,最终资源文件和.dex文件等打包成.apk文件。
3、Dalvik和Java运行环境的区别
Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。
Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
通过以上可以看出,这些不同点并不影响对DVM—gc相关的学习,所以我通过研究JVM相关的垃圾回收机制,来学习Android gc相关内容,如果文中有什么不对的地方,麻烦大家指出,共同学习,共同进步。
粗略的说:GC(Garbage Collection)动态回收无任何引用的对象占据的内存空间。GC通过确定对象是否被活动对象引用来确定是否收集该对象。
但是理解以上几句话我们可能需要了解以下知识。
JVM内存模型
Young Generation
图中的Eden + S0 + S1
Eden:存放新生的对象
Survivor Space:S0、S1 有两个,存放每次垃圾回收后存活的对象
(1)大多数新建的对象都位于Eden区。
(2)当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区。
(3)Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。
Old Generation
图中的Old Memory 主要存放应用程序中 长期存活的对象和经过多次Minor GC后依然存活下来的对象。通常会在老年代内存被占满时进行垃圾回收。老年代的垃圾收集叫做Major GC。Major GC会花费更多的时间。
Permanent Generation:
存放方法区,方法区中有 要加载的类信息、静态变量、final类型的常量、属性和方法信息。
JVM分别对新生代和旧生代采用的两种垃圾回收机制?
新生代的GC:
新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代。
旧生代的GC:
旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。
如何判断对象是否可以被回收?
两种常用的方法是引用计数和对象引用遍历。
(1)引用计数收集器
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象(不是引用)都有一个引用计数。当一个对象被创建时,且将该对象分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象+1),但当一个对象的某个引用超过了生命周期或者被设置为一个新值时,对象的引用计数减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。
缺点: 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
(2)跟踪收集器
现在大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,GC必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。
下一步,GC要删除不可到达的对象。删除时,有些GC只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多GC可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。
为此,GC需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有GC运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 GC不断增加或同时运行以减少或者清除应用程序的中断。有的GC使用单线程完成这项工作,有的则采用多线程以增加效率。
gc的原因(Log释义)
在官方文档上查了一下,gc reason有如下5个:
GC_CONCURRENT
A concurrent garbage collection that frees up memory as your heap begins to fill up.
GC_FOR_MALLOC
A garbage collection caused because your app attempted to allocate memory when your heap was already full, so the system had to stop your app and reclaim memory.
GC_HPROF_DUMP_HEAP
A garbage collection that occurs when you create an HPROF file to analyze your heap.
GC_EXPLICIT
An explicit garbage collection, such as when you call gc() (which you should avoid calling and instead trust the garbage collector to run when needed).
GC_EXTERNAL_ALLOC
This happens only on API level 10 and lower (newer versions allocate everything in the Dalvik heap). A garbage collection for externally allocated memory (such as the pixel data stored in native memory or NIO byte buffers).
GC_CONCURRENT、GC_FOR_MALLOC都是比较常见的gc原因。可以通过运行程序在Logcat上查看。
GC_CONCURRENT当堆要满的时候进行的垃圾收集
GC_FOR_MALLOC这就是“Stop the World”事件,因为所有的应用线程都会停下来直到操作完成。这时候堆已经满了,你申请分配内存就会触发这种原因的gc。
GC_HPROF_DUMP_HEAP
在我们创建HPROF文件分析堆内存的时候的gc原因。
GC_EXPLICIT
当我们显示的调用System.gc()方法,会出现的log。
GC_EXTERNAL_ALLOC
这个仅仅在API 10 及以下才会出现,我们不需要关注了。
文章部分参考自:
原文链接: journaldev 翻译:
- 进林 译文链接:
详细介绍Java垃圾回收机制:
百度百科:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:49564次
排名:千里之外
原创:42篇
转载:33篇
评论:13条
QQ: Email:Android(54)
讲GC原理很简单,但是把GC原理讲的简单就不简单了。
本文力求简单透彻,让所有Android开发人员都能明白基本的GC原理。
Jvm(Java虚拟机)内存模型
从Jvm内存模型中入手对于理解GC会有很大的帮助,不过这里只需要了解一个大概,说多了反而混淆视线。
Jvm(Java虚拟机)主要管理两种类型内存:堆和非堆。
堆是运行时数据区域,所有类实例和数组的内存均从此处分配。
非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JIT Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。
简言之,Java程序内存主要(这里强调主要二字)分两部分,堆和非堆。大家一般new的对象和数组都是在堆中的,而GC主要回收的内存也是这块堆内存。
配一张示意图总结一下:
堆内存(Heap Memory): 存放Java对象
非堆内存(Non-Heap Memory): 存放类加载信息和其它meta-data
其它(Other): 存放JVM 自身代码等
堆内存模型
既然重点是堆内存,我们就再看看堆的内存模型。
堆内存由垃圾回收器的自动内存管理系统回收。
堆内存分为两大部分:新生代和老年代。比例为1:2。
老年代主要存放应用程序中生命周期长的存活对象。
新生代又分为三个部分:一个Eden区和两个Survivor区,比例为8:1:1。
Eden区存放新生的对象。
Survivor存放每次垃圾回收后存活的对象。
看晕了吧,关注这几个问题:
为什么要分新生代和老年代?新生代为什么分一个Eden区和两个Survivor区?一个Eden区和两个Survivor区的比例为什么是8:1:1?
现在还不能解释为什么,但这几个问题都是垃圾回收机制所采用的算法决定的。
所以问题转化为,是何种算法?为什么要采用此种算法?
可回收对象的判定
讲算法之前,我们先要搞清楚一个问题,什么样的对象是垃圾(无用对象),需要被回收?
目前市面上有两种算法用来判定一个对象是否为垃圾。
1. 引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器&#20540;就加1;当引用失效时,计数器&#20540;就减1;任何时刻计数器为0的对象就是不可能再被使用的。
优点是简单,高效,现在的objective-c用的就是这种算法。
缺点是很难处理循环引用,比如图中相互引用的两个对象则无法释放。
这个缺点很致命,有人可能会问,那objective-c不是用的好好的吗?
我个人并没有觉得objective-c好好的处理了这个循环引用问题,它其实是把这个问题抛给了开发者。
2. 可达性分析算法(根搜索算法)
为了解决上面的循环引用问题,Java采用了一种新的算法:可达性分析算法。
从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。
OK,即使循环引用了,只要没有被GC Roots引用了依然会被回收,完美!
但是,这个GC Roots的定义就要考究了,Java语言定义了如下GC Roots对象:
虚拟机栈(帧栈中的本地变量表)中引用的对象。
方法区中静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI引用的对象。
Stop The World
有了上面的垃圾对象的判定,我们还要考虑一个问题,请大家做好心里准备,那就是Stop The World。
因为垃圾回收的时候,需要整个的引用状态保持不变,否则判定是判定垃圾,等我稍后回收的时候它又被引用了,这就全乱套了。所以,GC的时候,其他所有的程序执行处于暂停状态,卡住了。
幸运的是,这个卡顿是非常短(尤其是新生代),对程序的影响微乎其微 (关于其他GC比如并发GC之类的,在此不讨论)。
所以GC的卡顿问题由此而来,也是情有可原,暂时无可避免。
几种垃圾回收算法
有了上面两个大基础,我们的GC才能开始。
那么问题来了,已经知道哪些是垃圾对象了,怎么回收呢?目前主流有以下几种算法。
PS:大家可以先猜猜Java虚拟机(这里默认指Hotspot)采用的是那种算法,…,答对了,是分代回收算法,现在是不是明白了前面堆内存为什么要分新生代和老年代了吧。但是即使猜对了,也要看其他几种算法哦,不然不要说我没提醒你,你会直接看不懂分代回收算法的。
1. 标记清除算法 (Mark-Sweep)
标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
优点是简单,容易实现。
缺点是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
示意图如下(不用我解说了吧):
2. 复制算法 (Copying)
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
优缺点就是,实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。
从算法原理我们可以看出,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
示意图如下(不用我解说了吧):
3. 标记整理算法 (Mark-Compact)
该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
所以,特别适用于存活对象多,回收对象少的情况下。
示意图如下(不用我解说了吧):
4. 分代回收算法
分代回收算法其实不算一种新的算法,而是根据复制算法和标记整理算法的的特点综合而成。这种综合是考虑到java的语言特性的。
这里重复一下两种老算法的适用场景:
复制算法:适用于存活对象很少。回收对象多
标记整理算法: 适用用于存活对象多,回收对象少
刚好互补!不同类型的对象生命周期决定了更适合采用哪种算法。
于是,我们根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Old Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
这就是分代回收算法。
现在回头去看堆内存为什么要划分新生代和老年代,是不是觉得如此的清晰和自然了?
我们再说的细一点:
对于新生代采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,采用Copying算法效率最高。但是,但是,但是,实际中并不是按照上面算法中说的1:1的比例来划分新生代的空间的,而是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,比例为8:1:1.。为什么?下一节深入分析。由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
深入理解分代回收算法
对于这个算法,我相信很多人还是有疑问的,我们来各个击破,说清楚了就很简单。
为什么不是一块Survivor空间而是两块?
这里涉及到一个新生代和老年代的存活周期的问题,比如一个对象在新生代经历15次(仅供参考)GC,就可以移到老年代了。问题来了,当我们第一次GC的时候,我们可以把Eden区的存活对象放到Survivor A空间,但是第二次GC的时候,Survivor A空间的存活对象也需要再次用Copying算法,放到Survivor B空间上,而把刚刚的Survivor A空间和Eden空间清除。第三次GC时,又把Survivor B空间的存活对象复制到Survivor A空间,如此反复。
所以,这里就需要两块Survivor空间来回倒腾。
为什么Eden空间这么大而Survivor空间要分的少一点?
新创建的对象都是放在Eden空间,这是很频繁的,尤其是大量的局部变量产生的临时对象,这些对象绝大部分都应该马上被回收,能存活下来被转移到survivor空间的往往不多。所以,设置较大的Eden空间和较小的Survivor空间是合理的,大大提高了内存的使用率,缓解了Copying算法的缺点。
我看8:1:1就挺好的,当然这个比例是可以调整的,包括上面的新生代和老年代的1:2的比例也是可以调整的。
新的问题又来了,从Eden空间往Survivor空间转移的时候Survivor空间不够了怎么办?直接放到老年代去。
Eden空间和两块Survivor空间的工作流程
这里本来简单的Copying算法被划分为三部分后很多朋友一时理解不了,也确实不好描述,下面我来演示一下Eden空间和两块Survivor空间的工作流程。
现在假定有新生代Eden,Survivor A, Survivor B三块空间和老生代Old一块空间。
放到Eden区
把Eden区的存活对象copy到Survivor A区,然后清空Eden区(本来Survivor B区也需要清空的,不过本来就是空的)
放到Eden区
把Eden区和Survivor A区的存活对象copy到Survivor B区,然后清空Eden区和Survivor A区
放到Eden区
把Eden区和Survivor B区的存活对象copy到Survivor A区,然后清空Eden区和Survivor B区
Old区慢慢的整理一番,空间又够了
从这段流程中,我相信大家应该有了一个清晰的认识了,当然为了说明原理,这只是最简化版本。
触发GC的类型
了解这些是为了解决实际问题,Java虚拟机会把每次触发GC的信息打印出来来帮助我们分析问题,所以掌握触发GC的类型是分析日志的基础。
GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。
GC_CONCURRENT: 当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。
GC_EXPLICIT: 表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行的最后努力而触发的GC。
了解Java虚拟机GC原理,应该对于Dalvik虚拟机和Art虚拟机的GC原理有很大帮助,至于这三者的GC有什么区别,只能一步一步来了。
而知晓这些知识,我相信在做Android GC优化的时候一定会用的上。
如果看到这里还没理解,加我微信,手把手免费教会!
原文地址: //%E7%90%86%E8%A7%A3Java%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:14356次
排名:千里之外
转载:257篇
(4)(7)(19)(46)(165)(22)}

我要回帖

更多关于 java 垃圾回收算法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信