`

JVM 1.4.1 中的垃圾收集(分代垃圾收集和并发垃圾收集)

阅读更多

我们分析了引用计数、复制、标记-清除和标记-整理这些经典的垃圾收集技术。其中每一种方法在特定条件下都有其优点和缺点。例如,当有很多对象成为 垃圾时,复制可以做得很好,但是有许多长寿对象时它就变得很糟(要反复复制它们)。相反,标记-整理对于长寿对象可以做得很好(只复制一次),但是当有许 多短寿对象时就没有那么好了。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. 代间引用
代间引用

 

 

卡片标记

 

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 提供)
垃圾收集选项

 

增量收集

 

增量收集选项自 1.2 起就成为 JDK 的一部分。增量收集减少了垃圾收集暂停,以牺牲吞吐能力为代价,这使它只在更短的收集暂停非常重要时才值得考虑,如接近实时的系统。

 

Train 算 法是 JDK 用于增量收集的算法,它在堆中老的代和年轻的代之间创建一个新区域。这些堆区域划分为“火车(train)”,每个火车又分为一系列的“车厢 (car)”。每个车厢可以分别收集。结果,每个火车车厢组成单独的一代,这意味着不但要跟踪老到年轻的引用,而且还要跟踪从老的火车到年轻的火车以及老 的车厢到年轻的车厢的引用。这为赋值函数(mutator)和垃圾收集器带来了大量的额外工作,但是可以得到更短的收集暂停。

 

 

并行收集器和并发收集器

 

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 通过增加新的针对多处理器系统和非常大的堆的多线程收集选项,进一步改进了垃圾收集的效率。

 

 

 

转自http://www.ibm.com/developerworks/cn/java/j-jtp11253/

分享到:
评论

相关推荐

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

    并简单概述了老对象和年轻对象、分代收集、小的收集、代间引用、跟踪代间引用、卡片标记、JDK 1.4.1 默认收集器、并行收集器和并发收集器、微调垃圾收集器等理论或技术。得出:随着JVM的发展,默认垃圾收集器变得...

    JVM的垃圾回收机制详解和调优

    在具体实践中,例如Sun HotSpot 1.4.1 JVM,堆内存分为新域(年轻代)、旧域(老年代)和永久域(存放类和方法信息)。可以通过`-Xms`和`-Xmx`设置堆的初始和最大大小,`-XX:NewRatio`调整新旧域的比例,`-XX:...

    JVM调优文档,自己总结汇总关于GC和性能的文章

    Sun HotSpot 1.4.1 JVM使用分代垃圾收集器,将堆空间划分为三个主要部分: - **新生代(Young Generation)**:新创建的对象最初位于此区域。 - **老年代(Old Generation)**:经过一定次数GC后仍存活的对象将被移...

    Java垃圾回收机制详解和调优

    Java垃圾回收机制是Java虚拟机(JVM)中至...合理的调优能够确保应用程序的性能和稳定性,避免不必要的内存消耗和垃圾收集停顿。在实际应用中,应根据系统负载、内存使用情况以及性能指标持续监控和调整垃圾回收策略。

    resin-jvm 调优

    分代并发收集器 这种收集器在护理域使用排它复制收集器,在旧域中则使用并发收集器。由于它比单空间共同发生收集器中断频繁,因此它需要较少的内存,应用程序的运行效率也较高,注意,过小的护理域可以导致大量的...

    java JVM 内存回收

    JVM中存在多种垃圾收集机制,每种都有其特点和适用场景: 1. **标记-清除收集器**:首先标记可到达对象,然后清除未标记的对象。此过程可能导致内存碎片。 2. **标记-压缩收集器**:类似于标记-清除,但后续会将...

    jvm配置和优化.txt

    Shenandoah也是一种低延迟的垃圾收集器,旨在减少GC停顿时间。它可以实现接近零停顿的垃圾回收。 **2.7 Region-based GC(基于区域的垃圾回收器)** 此类垃圾回收器将堆空间划分为多个区域,每个区域根据其对象...

    垃圾回收.pdf

    - **Sun HotSpot 1.4.1**:使用分代收集器,堆分为新域、旧域和永久域。 - **堆大小控制**:通过`-Xms`和`-Xmx`设置堆的最小和最大大小。 - **新域比例控制**:`-XX:NewRatio`设置新域在堆中的比例,例如,新域与...

    Java的垃圾回收机制详解和调优大全

    Sun HotSpot 1.4.1 JVM使用分代收集器,堆分为新域、旧域和永久域。新域存放新创建的对象,经过一定次数的GC循环后晋升至旧域,永久域存储类和方法对象。通过JVM参数如-Xms和-Xmx控制堆的总大小,-XX:NewRatio设定...

    IBM JVM GC 技术文档

    IBM JVM的GC技术文档全面而深入地介绍了IBM JVM在内存管理和垃圾回收方面的核心概念和技术细节。通过理解这些内容,开发者能够更好地优化应用程序的性能,减少内存泄漏的风险,并有效管理JVM中的内存资源。

    解决J2EE系统应用性能问题常用优化项目

    在J2EE系统应用性能优化中,首要关注的是Java虚拟机(JVM)的堆管理和垃圾回收设置。堆被划分为年轻代、老年代和持久代,分别对应新创建的对象、较旧的对象以及存储静态数据和常量的部分。Hotspot JVM的内存配置包括...

    Axis2_1.4.1客户端内存溢出

    ### Axis2_1.4.1客户端内存溢出...综上所述,通过以上实验步骤,我们不仅能够复现Axis2_1.4.1客户端的内存溢出问题,还能深入理解Web服务开发中潜在的性能挑战及应对策略,这对于提升软件质量和稳定性具有重要意义。

    java内存讲解

    Java作为一种广泛使用的编程语言,其内存管理和垃圾回收机制是开发人员必须了解的核心概念之一。本文将详细探讨JVM的垃圾回收机制及其调优方法,深入分析Java内存分配原理,并讨论Java中的内存管理与内存泄露问题。 ...

    Java虚拟机

    常见的垃圾收集算法以及垃圾收集器的特点和工作原理;常见虚拟机监控与故障处理工具的原理和使用方法。第三部分分析了虚拟机的执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎。第四部分讲解了...

    java面试题

    Bea JRockit JVM支持4种垃圾收集器: 46 63.5. 如何从JVM中获取信息来进行调整 46 63.6. Pdm系统JVM调整 47 63.6.1. 服务器:前提内存1G 单CPU 47 63.6.2. 客户机:通过在JNLP文件中设置参数来调整客户端JVM 47 64....

    j2sdk-1_4_1-src.zip

    这部分源码是学习Java语言机制的基础,例如Object类、String类、ArrayList和LinkedList的实现,以及并发控制的synchronized关键字和Thread类。 5. **launcher**: 这部分源码通常涉及到Java应用程序的启动机制,如...

    BEA_WebLogic平台下J2EE调优攻略

    合理设置JVM的初始堆大小(-Xms)和最大堆大小(-Xmx),并选择合适的垃圾收集器(如ParallelGC、ConcurrentMarkSweepGC等)可以提高应用的运行效率。 **2.1.2 jRockit调优** 如果使用的是BEA jRockit虚拟机,则...

Global site tag (gtag.js) - Google Analytics