`
netcome
  • 浏览: 489047 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

    博客分类:
  • JAVA
阅读更多

老对象和年轻对象

在任何一个应用程序堆中,一些对象在创建后很快就成为垃圾,另一些则在程序的整个运行期间一直保持生存。经验分析表明,对于大多数面向对象的语言,包括 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 通过增加新的针对多处理器系统和非常大的堆的多线程收集选项,进一步改进了垃圾收集的效率。

下个月,我们将讨论一些有关垃圾收集的性能神话(hints and myths),包括对象分配的真实成本、显式赋空的代价和好处以及结束(finalization)的代价,以此来完成我们对垃圾收集的探讨。

出处:developerWorks

分享到:
评论

相关推荐

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

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

    可商用版PHP个人逍遥商城系统源码 手机版+电脑版

    安装步骤: 1.逍遥商城系统需要PHP+mysql运行环境,可以在windows或linux下运行,具体的php和mysql的版本没有特殊要求,兼容范围还是比较广的,推荐php5.6以上,mysql5.0以上。 2.把文件夹所有文件解压缩在站点目录中, 2.执行http://127.0.0.1/install/index.php进行安装 3.安装完成后,把install目录删掉。

    wordpress可视化数据采集Scrapes插件,WP博客网站自动采集发布

    wordpress可视化数据采集Scrapes插件,WP博客网站自动采集发布 支持 PHP7.4,PHP8.0 及以上不支持 上传插件到 wp-content/plugins 目录,然后解压 不需要写采集规则,傻瓜式操作,只需要对方的网址域名,在后台动动鼠标即可。

    STM32F103使用NRF2401通过ACK响应双向通讯(HAL库)

    STM32F103使用NRF2401通过ACK响应双向通讯(HAL库) 简单的应用。

    libmodbus编译好的windows x86 版本的dll

    Libmodbus 是一个用于 Modbus 协议的开源库,提供了丰富的函数接口,可方便实现设备间的 Modbus 通信,支持多种平台,能轻松移植到不同系统中,有助于开发人员快速构建 Modbus 相关应用程序。我已编译好windows x86 版本的dll

    LabVIEW在汽车EPS转向器海纳传感器标定中的应用与优化

    内容概要:本文详细介绍了LabVIEW在汽车EPS转向器海纳传感器标定中的应用,重点探讨了如何通过LabVIEW解决传统标定方法中存在的数据采集实时性不足、标定过程缺乏自动化、数据分析难度大的问题。文中展示了具体的代码实现,包括数据采集、分析、校准和报告生成等功能模块,并通过实际案例展示了该系统的高效性和准确性。此外,文章还讨论了一些常见的调试问题及其解决方案,如通信握手逻辑、信号异常检测、温度补偿等。 适合人群:从事汽车电子、传感器标定及相关领域的工程师和技术人员。 使用场景及目标:适用于需要提高传感器标定效率和精度的企业和个人。目标是通过LabVIEW的应用,实现传感器标定的自动化和智能化,从而提升产品质量和生产效率。 其他说明:文章强调了LabVIEW在处理复杂标定任务中的灵活性和强大功能,提供了多个实用的技术细节和实践经验,对于希望深入了解LabVIEW在工业应用中的开发者非常有价值。

    嵌入式系统开发中Simulink If模块的DBC与硬件信号自动导入及代码生成技术

    内容概要:本文深入探讨了Simulink If模块在嵌入式系统开发中的强大功能,特别是在汽车电子和工业自动化领域的应用。主要介绍了两种核心技术:一是DBC文件的自动导入生成模型及代码,二是硬件信号的导入生成模型及代码。DBC文件的自动导入能够快速构建CAN总线通信模型,简化信号解析和报文处理,生成的代码可以直接应用于AUTOSAR架构,实现ASW和BSW的无缝对接。硬件信号导入功能则允许开发者轻松地将硬件设备产生的信号集成到Simulink模型中,自动生成带有滤波和其他预处理功能的代码,适用于实时数据采集和控制算法实现。 适合人群:从事嵌入式系统开发、汽车电子、工业自动化等相关领域的工程师和技术人员。 使用场景及目标:① 快速搭建基于DBC文件的CAN总线通信模型,提高开发效率;② 实现硬件信号的实时处理和控制,优化数据采集和信号处理流程;③ 自动生成符合AUTOSAR标准的高质量代码,减少手动编码错误。 其他说明:文中提供了多个Matlab代码示例,展示了具体的操作步骤和生成代码的结构,帮助读者更好地理解和应用这些功能。同时,文中还分享了一些实用技巧,如DBC文件的功能模块拆分、硬件信号的时间戳对齐以及代码生成的优化设置等。

    流变学仿真方法:流变学仿真参数设置.zip

    流变学仿真方法:流变学仿真参数设置.zip

    【算法设计与分析】基于动态规划的0-1背包问题求解:二维与一维滚动数组实现及应用

    内容概要:本文详细介绍了0-1背包问题的两种动态规划解法,包括二维DP数组和优化后的一维滚动DP数组方法。首先定义了最大物品数量和背包容量等常量,接着通过两个数组分别存储每个物品的重量和价值。文中给出的代码实现了完整的算法逻辑:初始化边界条件,迭代更新状态转移方程,最终计算出能够放入背包的最大价值。此外还提供了回溯功能,可以找出具体的最优解组合。为了提高空间效率,文章进一步解释了一维滚动数组的使用方法,并附有完整的C++代码实现。 适合人群:计算机科学专业学生或有一定编程基础、对算法设计与分析感兴趣的读者。 使用场景及目标:①学习经典的动态规划问题解决思路;②理解如何通过状态转移方程来优化复杂度;③掌握二维DP向一维DP转换的技术手段以节省内存开销。 阅读建议:建议读者先理解0-1背包问题的基本概念,再逐步深入研究两种不同的解法,注意对比两者之间的异同点,特别是空间复杂度方面的改进。同时可以通过修改输入数据来测试不同情况下的运行结果,加深对算法的理解。

    natsort-3.0.0.tar.gz

    该资源为natsort-3.0.0.tar.gz,欢迎下载使用哦!

    一维抛物热传导方程的数值解法及其MATLAB实现

    内容概要:本文详细介绍了求解一维抛物热传导方程的各种经典数值方法,包括显式欧拉法、隐式欧拉法、Crank-Nicolson格式(即梯形公式)、二阶BDF格式以及不同的差分格式(如五点差分、九点差分和紧差分)。每种方法不仅给出了理论公式的推导,还提供了完整的MATLAB源码实现,并附有详细的代码解释和数值例子的数据图解分析。通过对不同方法的比较,展示了它们在稳定性和精度方面的优劣。 适合人群:具备一定数学和编程基础的学生、科研人员及工程师。 使用场景及目标:适用于需要解决热传导问题的研究项目,帮助使用者理解并选择合适的数值方法进行仿真计算,优化求解过程。 其他说明:文中强调了边界条件处理的重要性,并建议初学者从简单的Dirichlet边界条件入手练习。此外,还提到了一些常见的陷阱,如MATLAB矩阵索引与物理空间坐标的错位问题,提醒开发者注意这些问题以确保正确性。

    一个人的经历信息和个人简介

    一个人的经历信息和个人简介

    三菱FX3U PLC圆弧插补程序解析及其在运动控制中的应用

    内容概要:本文详细介绍了三菱FX3U PLC的圆弧插补程序,涵盖从中断扫描初始化、U型插补主程序、移动控制函数到急停复位程序的具体实现方法。通过具体的代码示例,展示了如何利用U型插补指令和服务调用来实现精确的圆弧轨迹控制。此外,文中还讨论了插补过程中的一些关键技术点,如中断优先级设置、插补结果存储、角度参数设置以及误差补偿等。同时,提供了关于脉冲输出、坐标计算和方向控制的实际操作技巧,强调了脉冲当量换算的重要性,并分享了一些调试经验和注意事项。 适合人群:从事工业自动化、运动控制领域的工程师和技术人员,特别是那些对PLC编程有一定基础并希望深入了解三菱FX3U PLC圆弧插补功能的人群。 使用场景及目标:适用于需要进行高精度圆弧轨迹控制的应用场景,如CNC控制系统、机器人运动控制等。目标是帮助读者掌握三菱FX3U PLC的圆弧插补编程技能,提高其在实际项目中的应用能力。 其他说明:文中提供的程序实例不仅有助于理解三菱FX3U PLC的工作原理,还能作为实际项目的参考模板。建议读者在实践中不断优化和完善相关程序,以适应不同的应用场景。

    西门子200 Smart PLC在60吨/小时反渗透+混床纯水项目中的应用与优化

    内容概要:本文详细介绍了在一个60吨/小时的反渗透+混床纯水项目中,如何利用西门子200 Smart PLC和Smart Line触摸屏进行系统设计与优化。主要内容涵盖硬件架构搭建(如CPU SR40、AI模块、TM模块)、模拟量处理(如电导率、压力、流量等信号的采集与转换)、数据滤波(如滑动平均值算法)、时钟同步(如自动和手动校准)、用户权限管理(如多级权限设置)、报警处理(如状态位轮询和异或运算)以及混床再生控制(如PID指令和状态机编程)。此外,还涉及了一些实用技巧,如数据追溯功能、报警弹窗中的应急处置指引、隐藏的debug模式等。 适合人群:从事工业自动化控制领域的工程师和技术人员,尤其是熟悉西门子PLC和触摸屏编程的人群。 使用场景及目标:适用于需要深入了解和掌握西门子200 Smart PLC在水处理项目中的具体应用和优化方法的专业人士。目标是提高系统稳定性、可靠性和易用性,确保水处理过程高效、精准地运行。 其他说明:文中提供了大量具体的程序代码片段和实践经验分享,对于实际项目实施具有很高的参考价值。同时,作者强调了细节处理的重要性,如模拟量的精确转换、报警系统的完善设计等,这些都是保障系统正常运行的关键因素。

    WMware的mac版本

    找了一大圈终于找到了,有些e xing博主发的还有密码和要米,本资源真实可用

    matlab-配备MPPT(P & O)控制增压转换器的PMSG风力涡轮机仿真模型

    增压转换器和涡轮机的功率为1千瓦。它们适合风速高达12 m/s。涡轮机参考旋转速度150转/分

    卷积神经网络(CNN)项目源码-基于CNN的行为姿态识别代码

    卷积神经网络(CNN)项目源码-基于CNN的行为姿态识别代码

    jspm酒店客房预定管理系统.docx

    jspm酒店客房预定管理系统

    西门子博途HMI中基于SCL的高效IO状态监控与动态页面切换方案

    内容概要:本文详细介绍了如何利用西门子博途(TIA Portal)平台,在HMI(人机界面)中实现高效的IO状态监控和动态页面切换。主要内容包括:PLC端使用SCL语言创建结构化IO状态数组并进行动态更新;HMI端通过下拉菜单和动态绑定技术实现单页多设备IO状态的快速切换。文中还提供了具体的代码示例和技术细节,如符号寻址的应用、数组索引的注意事项以及常见的错误规避方法。 适合人群:从事工业自动化控制系统开发的技术人员,特别是熟悉西门子PLC编程和HMI开发的工程师。 使用场景及目标:适用于需要对多个设备或模块进行集中监控的工业现场,能够显著减少HMI画面制作时间和维护成本,提高系统的灵活性和可扩展性。 其他说明:文中提到的方法已在实际项目中得到验证,特别是在处理大量IO点的情况下表现出色。同时强调了良好的架构设计对于系统长期维护的重要性。

    MATLAB图像检索技术综述:词袋模型、颜色特征、形状特征、Hu不变矩及LBP纹理特征的应用

    内容概要:本文详细介绍了MATLAB在图像检索领域的多种方法和技术,包括词袋模型、颜色特征、形状特征、Hu不变矩和LBP纹理特征。通过具体的代码实例,展示了如何利用这些方法进行图像特征提取和相似度计算。词袋模型借鉴文本处理思想,通过SIFT特征和聚类构建视觉词典;颜色特征主要通过颜色直方图表示图像颜色分布;形状特征则利用Hu不变矩实现平移、旋转和尺度不变性;LBP纹理特征用于描述图像的局部纹理信息。每种方法都有其独特的应用场景和优势。 适合人群:从事图像处理、计算机视觉研究的技术人员,尤其是有一定MATLAB基础的研究人员和开发者。 使用场景及目标:适用于需要高效、精确图像检索的场合,如商品搜索、医学影像分析、安防监控等领域。通过掌握这些方法,能够提高图像检索的速度和准确性,满足不同业务需求。 其他说明:文中不仅提供了详细的理论解释,还附带了大量实用的MATLAB代码片段,帮助读者快速理解和应用这些技术。同时强调了在实际应用中选择合适特征组合的重要性,避免过度依赖单一算法。

Global site tag (gtag.js) - Google Analytics