Java理论与实践: JVM 1.4.1中的垃圾堆积[Java编程]
本文“Java理论与实践: JVM 1.4.1中的垃圾堆积[Java编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
上个月,我们解析了引用计数、复制、标志-排除和标志-整理这些经典的垃 圾汇集技术.此中每一种办法在特定条件下都有其长处和缺陷.比方,当有很多 对象成为垃圾时,复制可以做得很好,但是有很多长寿对象时它就变得很糟(要 反复复制它们).相反,标志-整理关于长寿对象可以做得很好(只复制一次) ,但是当有很多短寿对象时就没有那么好了.JVM 1.2 及今后版本利用的技术称 为 分代垃圾汇集(generational garbage collection),它结合了这两种技术 以结合二者的所长,后果就是对象分配开销非常小.
老对象和年青对象
在任何一个利用程序堆中,一些对象在成立后很快就成为垃圾,另一些 则在程序的整个运行期间一向保持生存.经验解析表明,关于大大都面向对象的 语言,包含 Java 语言,绝大大都对象――可以多达 98%(这取决于您对年青对 象的衡量尺度)是在年青的时刻死亡的.可以用时钟秒数、对象分配今后�h内存管理子系统分配的总字节大概对象分配后阅历的垃圾汇集的次数 来计算对象的寿命.但是不管您若何计量,解析表明了同一件事――大大都对象 是在年青的时刻死亡的.大大都对象在年青时死亡这一事实关于汇集器的挑选很 有意义.分外是,当大大都对象在年青时死亡时,复制汇集器可以履行得相当好 ,因为复制汇集器完好不拜候死亡的对象,它们只是将活的对象复制到另一个堆 区域中,然后一次性收回全部的剩余空间.
那些阅历过第一次垃圾汇集 后仍能生存的对象,很大部份会成为长寿的大概永久的对象.按照短寿对象和长 寿对象的混合比例,差别垃圾汇集战略的性能会有非常大的差别.当大大都对象 在年青时死亡时,复制汇集器可以工作得很好,因为年青时死亡的对象永久不需 要复制.不过,复制汇集器处理长寿对象却很糟糕,它要从一个半空间向另一个 半空间反复来答复制这些对象.相反,标志-整理汇集器关于长寿对象可以工作 得很好,因为长寿对象趋向于沉在堆的底部,从而不用再复制.不过,标志-清 除和标志-理整汇集器要做很多额外的解析死亡对象的工作,因为在排除阶段它 们必须解析堆中的每一个对象.
分代汇集
分代汇集器(generializational collector)将堆分为多个代.在年青的代中 成立对象,满意某些晋升尺度的对象,如阅历了特定次数垃圾汇集的对象,将被 晋升到下一更老的代.分代汇集器对差别的代可以安闲利用差别的汇集战略,对 各代辨别举行垃圾汇集.
小的汇集
分代汇集的一个长处是它差别时汇集全部的代,因此可以使垃圾汇集暂停更 短.当分配器不能满意分配恳求时,它首先触发一个 小的汇集(minor collection),它只汇集最年青的代.因为年青代中的很多对象已经死亡,复制 汇集器完好不用解析死亡的对象,所以小的汇集的暂停可以相当短并普通可以回 收大量的堆空间.假如小的汇集释放了充足的堆空间,那么用户程序便可以当即 恢复.假如它不能释放充足的堆空间,那么它就持续汇集上一代,直到回收了足 够的内存.(在垃圾汇集器举行了全部汇集今后仍不能回收充足的内存时,它将 扩大堆大概抛出 OutOfMemoryError ).
代间引用
跟踪垃圾汇集器,如复制、标志-排除和标志-整理等垃圾汇集器,都是从根集 (root set)开始扫描,遍历对象间的引用,直到拜候了全部活的对象.
分代跟踪汇集器从根集开始,但是并不遍历指向更老一代中对象的引用,这 削减了要跟踪的对象图的大小.但是这也带来一个问题――假如更老一代中的对 象引用一个不能通过从根开始的全部其他引用链到达的更年青的对象该怎么办?
为了办理这个问题,分代汇集器必须显式地跟踪从老对象到年青对象的引用 并将这些纯熟年青的引用加入到小的汇集的根集合.有两种成立从老对象到年青 对象的引用的办法.要末是将老对象中包含的引用改正成指向年青对象,要末是 将引用其他年青对象的年青对象晋升为更老的一代.
跟踪代间引用
不管一个纯熟年青的引用是通过晋升还是指针改正成立的,垃圾汇集器在进 行小的汇集时需求有全部纯熟年青的引用.做到这一点的一种办法是跟踪老的代 ,但是这明显有很大的开销.更好的一种办法是线性扫描老的代以查找对年青对 象的引用.这种办法比跟踪更快并有更好的区域性(locality),但是仍旧有很 大的工作量.
赋值函数(mutator)和垃圾汇集器可以共同工作以在成立纯熟年青的引用时 保护它们的完好列表.当对象晋升为更老一代时,垃圾汇集器可以记录全部由于 这种晋升而成立的纯熟年青的引用,这样就只需求跟踪由指针改正所成立的代间 引用.
垃圾汇集器可以有几种办法跟踪由于改正现有对象中的引用而产生的纯熟年 轻的引用.它可以利用在引用计数汇集器中保护引用计数的一样办法(编译器可 以生成环绕指针赋值的附加指令)跟踪它们,也可以在老一代堆上利用虚拟内存 保护以捕捉向老对象的写入.另一种大概更有效的虚拟内存办法是在老一代堆中 利用页改正脏位(page modification dirty bit),以肯定为找到包含纯熟年 轻指针的对象时要扫描的块.
用一点小本领,便可以避免跟踪每一个指针改正并查抄它能否超越代边界的 开销.比方,不需求跟踪针对本地大概静态变量的存储,因为它们已经是根集的 一部份了.也可以避免跟踪存储在某些构造函数中的指针,这些构造函数只用于 初始化新建对象的字段(即所谓 初始化存储(initializing stores)),因为 (几近)全部对象都是分配到年青代中.不管是什么情形,运行库都必须保护一 个老对象到年青对象的引用集并在汇集年青代时将这些引用增添到根集合.
在图 1 中,箭头表示堆中对象间的引用.红色箭头表示必须增添到根集合供 小的汇集利用的纯熟年青的引用.蓝色箭头表示从根集大概年青代到老对象的引 用,在只汇集年青代时不需求跟踪它们.
图 1. 代间引用
以上是“Java理论与实践: JVM 1.4.1中的垃圾堆积[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
本文地址: | 与您的QQ/BBS好友分享! |