以下是笔者前面翻译的垃圾回收相关文章
Parallel Scavenge(并行回收)
本文我们将会描述并行垃圾回收(Parallel GC)是如何工作的。具体来说,这是一个在Eden空间运行Parallel Scavenge收集器、在老年代(Tenured generation)空间运行并行标记(Parallel Mark)和Sweep收集器的组合。你可以通过传入参数’ -XX:+UseParallelOldGC’来获取这些特性,虽然在某些机器类型上它是默认的。
如果你对垃圾收集器的内容还没有一个总体的了解,你可以先阅读关于垃圾回收的第一篇文章。
Eden and Survivor空间
在并行回收收集器中,eden和survivor空间的回收是使用大家所熟知的Hemispheric GC方法。对象初始分配存储到Eden空间,一旦Eden空间不足,Eden空间的垃圾回收(gc)将会被触发。垃圾回收时识别可达的对象并复制这些可达对象到活跃的survivor空间。然后将整个Eden空间视为一个空闲的、连续的、可以重新分配的内存块。
在这种情况下的分配过程就像切一块奶酪一样。每一块都会被连续的切下来,然后旁边被切下来的这些切片就会被“吃掉”,优势在于分配仅仅只需要指针相加。
为了识别可达的对象就要进行对象图的检索。检索从一组确保可达的“root”对象开始,例如每个线程都有一个root对象。然后搜索找到root集所指向的对象,然后不停的向外拓展直到找到所有的可达对象。下图很好的展示了这一过程。
并行回收(parallel scavenge)上下文中的并行(Parallel )所指的是同时运行多个线程来完成垃圾回收。千万不要和增量式GC(incremental GC)相混淆,即收集器可以和应用程序同时或者交叉运行。并行回收通过更好地使用现代多核CPU来提高总体GC吞吐量。并行特性是通过给每个线程一组root标记和对象表的一部分来实现。
有两个幸存者空间(survivor spaces),但是其中只有一个在任何时候都是处于激活状态。它们回收垃圾的方式和eden空间的回收方式一致。主要思想就是当对象从eden空间中升迁(eden空间进行垃圾回收时幸存下来的对象)时这些对象将会被拷贝到活跃的幸存者空间(survivor space)中。然后,当撤销空间的时候它们就会被拷贝到不活跃的幸存者空间(survivor space)中。一旦活跃的幸存者空间(survivor space)完成撤销,不活跃的幸存者空间(survivor space)立马被激活并且被撤销的活跃空间变成不活跃。这主要是通过将指针指向幸存者空间(survivor space)的开头来实现的,并且意味着幸存者空间(survivor space)中的所有不可达对象可以以分配给单个指针为代价而被释放。
新生代的设计与时间权衡(Young Gen design and time tradeoffs)
因为只是涉及到拷贝可达对象和实时变换指针,所以eden空间和幸存者空间(survivor space)的回收和可达对象的数量成正比。这一点非常重要,因为由于分代假设(generational hypothesis)我们知道大多数的对象在很早的时候就已经不可达,并且没有GC成本来释放与之相关的内存。
幸存者( survivor spaces)空间的设计灵感来源于这样一种理念:更早的回收比在老年代空间(tenured space)中回收花销更小。CG运行时对象持续以hemispheric的方式回收可以提高系统整体的吞吐量。
最终,eden空间被组织成一个单独连续的空间,使得对象分配的开销变得很低。C程序可能会使用“malloc”命令分配内存块,这将会涉及到遍历一些列空闲的内存空间尝试找到足够大的内存块。当你使用arena allocator并分配你需要的连续空间时,你需要做的就是检查是否有足够的空闲空间然后以对象的大小为增量来移动指针。
并行标记和清除(Parallel Mark and Sweep)
经过一定次数的回收之后,幸存下来的对象进入了老年代空间(tenured space)。它们幸存的次数称为“tenuring threshold(老年代阈值,也称提升阈值)”,老年代空间的回收工作和eden空间的回收工作有一些不同,它所使用的是一个被称为标记和清除的算法(mark and sweep)。每个对象都有一个与之相关联的标记位。这些标记初始值都被设置为false,在对对象图进行检索时,如果对象可达,那可达对象的标记就会被设置成true。
识别可达对象的图检索与我们上文对新生代(young generation)的检索描述类似。不同点在于使用标记取代了可达对象的拷贝。标记完成之后就可以遍历对象表并释放不可达对象所占用的空间。这个过程通过多线程并行完成,每个线程检索堆中的一个区。
不幸的是这一删除不可达对象的过程会让老年代空间(tenured space)看起来像瑞士奶酪(Swiss Cheese)。你得到了一些可以存储对象的使用过的内存,并且这些内存空间和过去的一些可达对象所使用的内存空间之间形成了空隙。这种内存碎片对于应用程序的执行效率来说是非常不利的,因为这让在这些空洞的内存空间中分配比它大的对象变得不可能。
Cheese after Mark and Sweep.
为了减少类似于瑞士奶酪(Swiss Cheese)这样的问题,并行标记/清除(Parallel Mark/Sweep)压缩了堆以便在老年代空间(tenured space)的开头分配连续可达的对象。对象删除之后检索老年代空间(tenured space)的区域以确定那些区域占有率低,那些区域占有率高。占有率低的区域中的可达对象会被移动到占有率高的区域。这些操作自然是在上一个压缩阶段的内存的底端,在这个阶段对象的移动实际上是通过分配给目标区域的线程去执行,而不是源区域( source region)。
Low occupancy cheese.
小结
并行回收将堆分为四个空间:eden, 两个幸存者空间(survivor spaces)、老年代空间(tenured space)。
并行回收使用了并行、拷贝收集器回收Eden和幸存者空间(survivor spaces)。
在老年代空间(tenured space)使用了不同的算法。该算法标记了所有的对象,删除不可达对象然后压缩空间。
并行回收拥有良好的吞吐量,但是当其运行时会暂停整个应用。
原文:https://www.javacodegeeks.com/2013/06/garbage-collection-in-java-2.html