有两种一般方法可以减少垃圾收集暂停时间及其对应用程序性能的影响:
垃圾回收本身可以利用多个CPU的存在并并行执行。尽管在此期间应用程序线程保持完全挂起,但垃圾回收可以在短时间内完成,从而有效地减少了挂起时间。
第二种方法是保持应用程序运行,并在应用程序执行的同时执行垃圾回收。
这两种逻辑解决方案促成了串行,并行和并发垃圾收集策略的开发,这些策略代表了所有现有Java垃圾收集实现的基础(请参见图2.6)。
图2.6:比较垃圾收集挂起时,垃圾收集算法之间的差异变得最明显。
串行收集器挂起应用程序,并在一个线程中执行标记清除算法。它是Java中最简单,最古老的垃圾回收形式,仍然是Oracle HotSpot JVM中的默认形式。
并行收集器使用多个线程来完成其工作。因此,它可以通过利用多个CPU来减少GC暂停时间。它通常是吞吐量应用程序的最佳选择。
并发收集器在执行应用程序时并发执行大部分工作。它只需要暂停应用程序很短的时间。这对于响应时间敏感的应用程序具有很大的好处,但并非没有缺点。
(大部分)并发标记和清扫
并发垃圾收集策略使相对简单的标记清除算法变得有些复杂。标记阶段通常分为以下几种变体:
在初始标记中,GC根对象标记为活动。在此阶段,应用程序的所有线程都被挂起。
在并发标记期间,将遍历已标记的根对象,并标记所有可到达的对象。此阶段与应用程序执行完全并发,因此所有应用程序线程都处于活动状态,甚至可以分配新对象。因此,可能会有另一个阶段来标记在并行标记期间已分配的对象。有时将其称为预清理,并且仍与应用程序执行同时进行。
在最后的标记中,所有线程都被挂起,所有剩余的新分配的对象都标记为活动。这在图2.6中用重新标记标签指示。
并发标记在不暂停应用程序的情况下大部分(但不是全部)有效。权衡是一个更复杂的算法,是正常的GC中不需要的附加阶段:最终标记。
Oracle JRockit JVM在保留区域的帮助下改进了该算法,如果您有兴趣,可以在JRockit文档中对其进行详细描述。新对象将单独保存,在第一个GC期间不会被视为垃圾。这消除了最终标记或重新标记的需要。
在CMS的扫描阶段,将找到未被标记对象占用的所有内存区域,并将其添加到空闲列表中。换句话说,对象被GC清除。此阶段可以与应用程序至少部分并发运行。例如,JRockit将堆划分为两个大小相等的区域,然后一扫一扫。在此阶段,没有线程停止,但是分配仅发生在未被主动清除的区域中。
CMS算法的缺点可以很快被发现:
由于标记阶段与应用程序的执行同时进行,因此为对象分配的空间可能会超过CMS的容量,从而导致分配错误。
空闲列表立即导致内存碎片,所有这些都导致了。
该算法比其他两种算法更复杂,因此需要更多的CPU周期。
与其他方法相比,该算法需要更精细的调整并具有更多的配置选项。
除了这些缺点之外,CMS几乎总是会带来更大的可预测性和更好的应用程序响应时间。
减少压实的影响
现代垃圾收集器利用多个CPU并行执行其压缩过程。不过,在此过程中,几乎所有人都必须暂停应用程序。具有数GB内存的JVM可以挂起几秒钟或更长时间。要解决此问题,各种JVM各自实现一组参数,这些参数可用于以较小的增量步骤压缩内存,而不是将其作为一个大块。参数如下:
压缩不是针对每个GC周期执行的,而是仅在达到一定碎片级别时执行的(例如,如果50%以上的可用内存不连续)。
可以配置目标分段。垃圾收集器不会压缩所有内容,而只会压缩直到指定百分比的可用内存作为连续块可用为止。
这是可行的,但是优化过程很繁琐,需要进行大量测试,并且需要为每个应用程序一次又一次地完成以达到最佳结果。
- 关注Java架构师Carl -
转了吗
赞了吗
在看
领取专属 10元无门槛券
私享最新 技术干货