Java理论与实践: JVM 1.4.1中的垃圾收集
当前位置:以往代写 > JAVA 教程 >Java理论与实践: JVM 1.4.1中的垃圾收集
2019-06-14

Java理论与实践: JVM 1.4.1中的垃圾收集

Java理论与实践: JVM 1.4.1中的垃圾收集

副标题#e#

上个月,我们阐明白引用计数、复制、标志-排除和标志-整理这些经典的垃 圾收集技能。个中每一种要领在特定条件下都有其利益和缺点。譬喻,当有许多 工具成为垃圾时,复制可以做得很好,可是有很多长命工具时它就变得很糟(要 重复复制它们)。相反,标志-整理对付长命工具可以做得很好(只复制一次) ,可是当有很多短寿工具时就没有那么好了。JVM 1.2 及今后版本利用的技能称 为 分代垃圾收集(generational garbage collection),它团结了这两种技能 以团结二者的优点,功效就是工具分派开销很是小。

老工具和年青工具

在任何一个应用措施堆中,一些工具在建设后很快就成为垃圾,另一些 则在措施的整个运行期间一直保持保留。履历阐明表白,对付大大都面向工具的 语言,包罗 Java 语言,绝大大都工具――可以多达 98%(这取决于您对年青对 象的权衡尺度)是在年青的时候灭亡的。可以用时钟秒数、工具分派今后�h内存打点子系统分派的总字节可能工具分派后经验的垃圾收集的次数 来计较工具的寿命。可是不管您如何计量,阐明表白了同一件事――大大都工具 是在年青的时候灭亡的。大大都工具在年青时灭亡这一事实对付收集器的选择很 有意义。出格是,当大大都工具在年青时灭亡时,复制收集器可以执行得相当好 ,因为复制收集器完全不会见灭亡的工具,它们只是将活的工具复制到另一个堆 区域中,然后一次性收回所有的剩余空间。

那些经验过第一次垃圾收集 后仍能保留的工具,很大部门会成为长命的可能永久的工具。按照短寿工具和长 寿工具的殽杂比例,差异垃圾收集计策的机能会有很是大的不同。当大大都工具 在年青时灭亡时,复制收集器可以事情得很好,因为年青时灭亡的工具永远不需 要复制。不外,复制收集器处理惩罚长命工具却很糟糕,它要从一个半空间向另一个 半空间重复往返覆制这些工具。相反,标志-整理收集器对付长命工具可以事情 得很好,因为长命工具趋向于沉在堆的底部,从而不消再复制。不外,标志-清 除和标志-理整收集器要做许多特另外阐明灭亡工具的事情,因为在排除阶段它 们必需阐明堆中的每一个工具。

分代收集

分代收集器(generializational collector)将堆分为多个代。在年青的代中 建设工具,满意某些晋升尺度的工具,如经验了特定次数垃圾收集的工具,将被 晋升到下一更老的代。分代收集器对差异的代可以自由利用差异的收集计策,对 各代别离举办垃圾收集。

小的收集

分代收集的一个利益是它差异时收集所有的代,因此可以使垃圾收集暂停更 短。当分派器不能满意分派请求时,它首先触发一个 小的收集(minor collection),它只收集最年青的代。因为年青代中的很多工具已经灭亡,复制 收集器完全不消阐明灭亡的工具,所以小的收集的暂停可以相当短并凡是可以回 收大量的堆空间。假如小的收集释放了足够的堆空间,那么用户措施就可以当即 规复。假如它不能释放足够的堆空间,那么它就继承收集上一代,直到接纳了足 够的内存。(在垃圾收集器举办了全部收集今后仍不能接纳足够的内存时,它将 扩展堆可能抛出 OutOfMemoryError )。

代间引用

跟踪垃圾收集器,如复制、标志-排除和标志-整理等垃圾收集器,都是从根集 (root set)开始扫描,遍历工具间的引用,直到会见了所有活的工具。

分代跟踪收集器从根集开始,可是并不遍历指向更老一代中工具的引用,这 淘汰了要跟踪的工具图的巨细。可是这也带来一个问题――假如更老一代中的对 象引用一个不能通过从根开始的所有其他引用链达到的更年青的工具该怎么办?

为了办理这个问题,分代收集器必需显式地跟踪从老工具到年青工具的引用 并将这些老练年青的引用插手到小的收集的根会合。有两种建设从老工具到年青 工具的引用的要领。要么是将老工具中包括的引用修改为指向年青工具,要么是 将引用其他年青工具的年青工具晋升为更老的一代。

跟踪代间引用

不管一个老练年青的引用是通过晋升照旧指针修改建设的,垃圾收集器在进 行小的收集时需要有全部老练年青的引用。做到这一点的一种要领是跟踪老的代 ,可是这显然有很大的开销。更好的一种要领是线性扫描老的代以查找对年青对 象的引用。这种要领比跟踪更快并有更好的区域性(locality),可是仍然有很 大的事情量。

赋值函数(mutator)和垃圾收集器可以配合事情以在建设老练年青的引用时 维护它们的完整列表。当工具晋升为更老一代时,垃圾收集器可以记录所有由于 这种晋升而建设的老练年青的引用,这样就只需要跟踪由指针修改所建设的代间 引用。

#p#分页标题#e#

垃圾收集器可以有几种要领跟踪由于修改现有工具中的引用而发生的老练年 轻的引用。它可以利用在引用计数收集器中维护引用计数的同样要领(编译器可 以生成环绕指针赋值的附加指令)跟踪它们,也可以在老一代堆上利用虚拟内存 掩护以捕捉向老工具的写入。另一种大概更有效的虚拟内存要领是在老一代堆中 利用页修改脏位(page modification dirty bit),以确定为找到包括老练年 轻指针的工具时要扫描的块。

用一点小能力,就可以制止跟踪每一个指针修改并查抄它是否超过代界线的 开销。譬喻,不需要跟踪针对当地可能静态变量的存储,因为它们已经是根集的 一部门了。也可以制止跟踪存储在某些结构函数中的指针,这些结构函数只用于 初始化新建工具的字段(即所谓 初始化存储(initializing stores)),因为 (险些)所有工具都是分派到年青代中。不管是什么环境,运行库都必需维护一 个老工具到年青工具的引用集并在收集年青代时将这些引用添加到根会合。

在图 1 中,箭头暗示堆中工具间的引用。赤色箭头暗示必需添加到根会合供 小的收集利用的老练年青的引用。蓝色箭头暗示从根集可能年青代到老工具的引 用,在只收集年青代时不需要跟踪它们。

图 1. 代间引用

Java理论与实践: JVM 1.4.1中的垃圾收集


#p#副标题#e#

卡片标志

Sun JDK 利用一种称为 卡片标志(card marking)算法的改造算法以标识对 老一代工具的字段中包括的指针的修改。在这种要领中,堆分为一组 卡片,每 个卡片一般都小于一个内存页。JVM 维护着一个卡片映射,对应于堆中的每一个 卡片都有一个位(在某些实现中是一个字节)。每次修改堆中工具中的指针字段 时,就在卡片映射中配置对应那张卡片的相应位。在垃圾收集时,就对与老一代 中卡片相关联的标志位举办查抄,对脏的卡片扫描以寻找对年青代有引用的工具 。然后排除标志位。卡片标志有几项开销――卡片映射所需的特别空间、对每一 个指针存储所做的特别事情,以及在垃圾收集时做的特别事情。对每一个非初始 化堆指针存储,卡片标志算法可以只增加两到三个呆板指令,并要求在小的收集 时对所有脏卡片上的工具举办扫描。

JDK 1.4.1 默认收集器

在默认环境下,JDK 1.4.1 将堆分为两部门,一个年青的代和一个老的代( 实际上,尚有第三部门――永久空间,它用于存储装载的类和要领工具)。借助 于复制收集器,年青的代又分为一个建设空间(凡是称为 Eden)和两个保留半 空间。

老的代利用标志-整理收集器。工具在经验了屡次复制后晋升到老的代。小的 收集将活的工具从 Eden 和一个保留半空间复制到另一个保留半空间,并大概提 升一些工具到老的代。大的收集(major collection)既会收集年青的代,也会 收集老的代。System.gc() 要领老是触发一个大的收集,这就是应该只管罕用( 假如不能完全不消的话) System.gc() 的原因之一,因为大的收集要比小的收 集耗费长得多的时间。没有步伐以编程方法触发小的收集。

其他收集选项

除了默认环境下利用的复制收集器和标志-整理收集器,JDK 1.4.1 还包括其 他四种垃圾收集算法,每一种合用于差异的目标。JDK 1.4.1 包括一个增量收集 器(自 JDK 1.2 就已经呈现了)和三种在多处理惩罚器系统中举办更有效收集的新 收集器――并行复制收集器、并行排除(scavenging)收集器和并发标志-排除 收集器。这些新收集器是为了办理在多处理惩罚器系统中垃圾收集器成为伸缩性瓶颈 这一问题的。图 2 显示了在什么时候选择备用收集选项的指导。

图 2. 1.4.1 垃圾收集选项(Folgmann IT-Consulting 提供)

Java理论与实践: JVM 1.4.1中的垃圾收集

增量收集

增量收集选项自 1.2 起就成为 JDK 的一部门。增量收集淘汰了垃圾收集暂 停,以牺牲吞吐本领为价钱,这使它只在更短的收集暂停很是重要时才值得思量 ,如靠近及时的系统。

Train算法是 JDK 用于增量收集的算法,它在堆中老的代和年青的代之间创 建一个新区域。这些堆区域分别为“火车(train)”,每个火车又分为一系列 的“车厢(car)”。每个车厢可以别离收集。功效,每个火车车厢构成单独的 一代,这意味着不单要跟踪老练年青的引用,并且还要跟踪从老的火车到年青的 火车以及老的车厢到年青的车厢的引用。这为赋值函数(mutator)和垃圾收集 器带来了大量的特别事情,可是可以获得更短的收集暂停。

并行收集器和并发收集器

#p#分页标题#e#

JDK 1.4.1 中新的收集器都是为办理多处理惩罚器系统中垃圾收集器的问题而设 计的。因为大大都垃圾收集算法会在一段时间里使系统遏制,单线程的收集器很 快会成为伸缩性瓶颈,因为在垃圾收集器将用户措施线程挂起时,除了一个处理惩罚 器之外,其他的处理惩罚器都是空闲的。新收集器中的两个――并行复制收集器和并 发标志-排除收集器――设计为淘汰收集暂停时间。另一个是并行排除收集器, 它是为在大堆上的更高吞吐本领而设计的。

并行复制收集器用 JVM 选项 -XX:+UseParNewGC 启用,是一个年青代复制收 集器,它将垃圾收集的事情分为与 CPU 数量一样多的线程。并发标志-排除收集 器由 -XX:+UseConcMarkSweepGC 选项启用,它是一个老代标志-排除收集器,它 在初始标志阶段(及在今后暂短从头标志阶段)暂短地遏制整个系统,然后规复 用户措施,同时垃圾收集器线程与用户措施并发地执行。并行复制收集器和并发 标志-排除收集器根基上是默认的复制收集器和标志-整理收集器的并发版本。由 -XX:+UseParallelGC 启用的并行排除收集器是年青代收集器,针对多处理惩罚器系 统上很是大(吉字节以及更大的)堆举办了优化。

选择一种算法

有六种算法可以选择,您大概不知道要利用哪一种。图 2提供了一些指导, 将收集器分为单线程和并发的,以及分为短暂停和高吞吐本领的。只要您把握了 应用措施和陈设情况的信息,就足以选择符合的算法。对付很多应用措施,默认 的收集器可以事情得很好――因此假如您没有机能问题,那么就没须要插手更多 的巨大性。不外,假如您的应用措施是陈设在多处理惩罚器系统上可能利用很是大的 堆,那么改变收集器选项大概会有庞大的机能晋升。

微调垃圾收集器

JDK 1.4.1 还包罗大量的微调垃圾收集的选项。调解这些选项并权衡它们的 结果大概会耗费您大量时间,因此在试图微调垃圾收集器之前先对您的应用措施 举办彻底的设置(profile)和优化,这样您的微调事情大概会获得更好的功效 。

微调垃圾收集首先要做的是查抄冗长的 GC 输出。这会使您获得垃圾收集操 作的频率、按时和一连时间等信息。最简朴的垃圾收集微调就是扩大最大堆的大 小( -Xmx )。跟着堆的增大,复制收会议变得更有效,所以在增大堆时,您就 淘汰了每个工具的收集本钱。除了增加最大堆的巨细,还可以用选项 – XX:NewRatio 增加分派给年青代的空间份额。也可以用 -Xmn 选项显式指定年青 代的巨细。

竣事语

跟着 JVM 的成长,默认垃圾收集器变得越来越好了。JDK 1.2 及今后版本所 利用的分代垃圾收集器提供了比早期 JDK 所利用的标志-排除-整理收集器好得 多的分派和收集机能。JDK 1.4.1 通过增加新的针对多处理惩罚器系统和很是大的堆 的多线程收集选项,进一步改造了垃圾收集的效率。

下个月,我们将接头一些有关垃圾收集的机能神话(hints and myths),包 括工具分派的真实本钱、显式赋空的价钱和长处以及竣事(finalization)的代 价,以此来完成我们对垃圾收集的探讨。

    关键字:

在线提交作业