深入理解Java虚拟机---(5)HotSpot算法实现+GC的收集器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/May_3/article/details/79518506

写在前面:

    在上一篇博客中,为大家总结了GC的相关回收算法,以及怎么判断对象是否已经"死亡",在这篇博客中,将会以HotSpot虚拟机为例,讲解GC的收集器。


HotSpot的算法实现:

    (1)GC停顿

    因为GC Roots的节点主要是全局性的引用(例如常量或类静态属性),如果逐个检查引用,会消耗大量的时间。GC分析工作会让所有的线程停顿在某个时间点上。

    但时间上,HotSpot不一定要检查完所有的引用位置。在HotSpot的实现中,使用一组OopMap数据结构来存放位置记录。

    (2)安全点

    HotSpot并不会为每一条指令都生成对应的 OopMap,因为那样会消耗额外的空间,反而加大了GC的回收成本。

    在HotSpot中,只是在"特定的位置"记录了这些信息。这些特定位置就成为"安全点"。程序只有执行到安全点时,才会停顿下来开始GC。

    安全点的选择标准,一般是程序是否具有让程序长时间执行的特征,例如:方法调用、循环跳转、异常跳转。这些点才成为安全点--Safepoint。

    那么怎么让线程都到最近的"安全点"停下,两种方案:

    抢占式中断:首先将所有线程中断,如果线程中断的地方不在安全点,则恢复线程,让他到安全点上。--现在不采用。

    主动式中断:当GC需要中断线程的时候,不直接对线程操作,设置一个标志,各个线程执行时主动去轮询这个标志,如果中断标志位真时就自己中断挂起,轮询的地方和安全点事重合的。

    (3)安全区域

    安全点可以解决那些正在执行的线程的问题,而对于处于BLOCKED状态、睡眠状态,这些线程CPU没有分配时间片,所以就需要安全区域来解决。

    安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域内开始GC都是安全的。安全区域可以看做是扩展的安全点。

------------------------------------------------------------------------

垃圾收集器:

    如果说收集算法是内存回收的理论基础,那么垃圾收集器则是内存回收的具体实现。由于不同厂商,不同版本的虚拟机提供不同的垃圾收集器,这篇博客我们讨论的是JDK7之后,HotSpot虚拟机中使用的常见的垃圾收集器。如果两个收集器之间存在连线,说明他们可以搭配使用。


(1)Serial收集器:

    首先Serial收集器是单线程收集器,它的单线程收集体现在两点。第一点:只会使用一个CPU或一条收集线程进行垃圾收集工作。第二点:在其进行垃圾收集时,必须暂停其他所有的工作线程。也就是我们所说的“stop the world”。

    现在,Serial收集器是JVM运行在Client模式下的默认新生代收集器。

(2)ParNew收集器:

    ParNew收集器是Serial收集器的多线程版本。与Serial收集器唯一不同的就是,使用多条线程进行垃圾收集。现在,ParNew收集器是JVM运行在Server模式下的首选新生代收集器。这是因为ParNew收集器是除了Serial收集器之外,唯一一个可以与CMS收集器配合的收集器。

    在单线程运行环境下,ParNew收集器不如Serial收集器

(3)Parallel Scavenge收集器:

    Parallel Scavenge收集器是一个新生代收集器。是复制算法的收集器,是并行的多线程收集器。Parallel Scavenge收集器的目的是达到一个可控制的吞吐量。

    吞吐量(Throughput):CPU用于运行用户代码时间与CPU总消耗时间的比值。

     =    CPU用于运行用户代码时间/(CPU用于运行用户代码时间+垃圾收集时间)

    停顿时间越短,吞吐量越高,就可以更高效率的利益CPU时间,尽快完成运算任务。

    Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量。分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMills参数和直接设置吞吐量大小的-XX:GCTImeTatio参数。

(4)Serial Old收集器:

    Serial Old收集器是Serial收集器的老年代版本。同样是一个单线程收集器,使用“标记-清除”算法。主要在于给Client模式下的虚拟机使用。在Server模式下,有两种使用方式,第一种与Parallel Scavenge收集器搭配,第二种是作为CMS的后预案。

(5)Parallel Old收集器:

    Parallel Old收集器是Parallel Scavenge的老年代版本。使用多线程+“标记-整理”算法。这个收集器从JDK1.6开始提供。

    在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old组合。

(6)CMS收集器(Concurrent Mark Sweep):

    CMS收集器从JDK1.5开始,是一种以获取最短回收停顿时间为目标的收集器。由于大部分的Java应用集中于B/S系统的服务端,CMS收集器很好的迎合这类看中服务响应速度,停顿时间短的特点。

    CMS收集器    采用“标记-清除”算法,收集的整个过程分为4个步骤:初始标记+并发标记+重新标记+并发清除。

    初始标记阶段会标记GC Roots能直接关联到的对象,速度较快。并发标记阶段是GC Roots Tracing的过程。重新标记阶段为了修正因为用户程序继续运作而导致的标记产生变动的一部分对象标记记录。

    从总体上看,CMS收集器的内存回收过程与用户线程是一起并发执行的。

    CMS收集器虽然看起来很完美,但是仍然有他的缺点:

  •     CMS收集器对CPU资源十分敏感。在内存回收时,虽然不会导致用户线程停顿,但是会因为占用一部分CPU资源,使得应用程序变慢,总吞吐量降低。CMS收集器默认启动的回收线程数=(CPU数量+3)/4,也就是说当CPU数量大于4时,CMS收集器占据不少于25%的CPU资源,随CPU数量增加而下降。当CPU数量小于4时,CMS收集器对用户线程的影响会变得很大。
  •     CMS收集器无法处理浮动垃圾。浮动垃圾是指用户线程在运行时,伴随程序运行还会有新的垃圾产生,当次无法收集的垃圾,只能在下一次进行回收,这部分称之为浮动垃圾。浮动垃圾可能会导致CMS收集器收集失败而导致另一次Full GC的过程。
  •     CMS收集器可能会提前触发一次Full GC过程。因为CMS收集器采用的是“标记-清除”算法,意味着在收集结束时,会产生大量的空间碎片。当空间碎片过多时,大对象分配会带来麻烦,所以可能会提前触发一次Full GC过程。

(7)G1收集器:

    G1收集器从JDK1.7开始,是一款面向服务端应用的垃圾收集器。在未来替代CMS收集器。

    G1收集器具备以下特点:

  •     并行与并发:利用多核的优势,使用多个CPU来缩短Stop the world的停顿时间。
  •     分代收集:G1收集器可以不需要其他收集器配合,独立管理整个GC堆。采用不同方式去处理新生代和老年代。
  •     空间整合:G1收集器从整体上看,使用“标记-整理”算法实现。从两个局部看基于“复制算法”。所以,在内存收集过程中,不会产生大量的内存空间碎片,在收集之后,可以提供可观的内存空间。不会像CMS收集器一样,提前触发一次Full GC。
  •     可观测的停顿:建立一个可观测的停顿时间模型。能让使用者在一个长度为M毫秒的时间片内,消耗在垃圾收集上的时间不得超过N毫秒。

    G1不仅仅将Java堆划分为新生代和老年代,而是划分了多个大小相等的独立Region,G1可以跟踪各个Region垃圾堆积的价值大小,然后优先区回收价值最大的Region。

    G1收集器的运作可以划分为4个步骤:初始标记+并发标记+最终标记+筛选标记。初始标记阶段会标记与GC Roots能关联到的对象,并发标记阶段会进行可达性分析,找出存活的对象。最终标记阶段是为修正用户程序继续进行而导致标记产生变动的那一部分标记记录。筛选标记阶段,会对各个Region区域的回收价值和成本进行排序,根据用户希望GC停顿时间来制定回收计划。


猜你喜欢

转载自blog.csdn.net/May_3/article/details/79518506