深入理解java虚拟机之自动内存管理机制(三)

 

各类垃圾收集器与GC日志

(一)垃圾收集器

  一、Serial收集器

    最基本、历史最悠久的收集器。使用复制算法,用在新生代,通常老年代用Serial old配合。GC过程需要stop the world。适用于client模式下的虚拟机。

  二、ParNew收集器

    Serial多线程版本,采用复制算法时,开启多线程进行复制。能与Serial old配合,且是唯一能与cms收集器配合使用的新生代收集器。适用于server模式下的虚拟机。在多cpu的环境
  下效果更好。

  三、Parallel Scavenge收集器

    关注点是达到一个可控制的吞吐量,使用复制算法。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

  四、Serial Old收集器

    Serial收集器的老年代版本,单线程收集器,使用标记-整理算法,适用于Client模式下的虚拟机。

  五、Parallel Old收集器

    Parallel Scavenge老年代版本,使用标记-整理算法。

  六、CMS收集器

    以获得最短回收停顿时间为目标的收集器。运作过程分为4个步骤。

    1. 初始标记。该阶段就是标记一下GC Roots能直接关联的对象。

    2. 并发标记。就是GC Roots tracing,该过程可与用户线程并发执行。

    3. 重新标记。修正并发标记期间发生变动的对象的标记。

    4. 并发清除。该过程可与用户线程并发执行。

    缺点:

    1. 对cpu资源敏感。如果不是多核,则由于存在两个并发过程,会导致用户程序执行缓慢。

    2. 无法处理浮动垃圾,并且由于在清除阶段与用户线程并发执行,可能由于内存不足而导致一次full gc的产生。所谓浮动垃圾,就是在重新标记过后产生的垃圾。解决办法是预留足够内存。

    3. 由于采用标记-清除算法,所以容易产生大量内存碎片。

  七、G1收集器

    G1是革命性的。它有以下几个特点。

    1. 并行与并发。G1可以让本来需要stw的动作通过并发的方式让java程序继续执行。

    2. 分代收集。G1收集器自身就可以管理新生代和老年代的对象。

    3. 空间整合。整体使用标记-整理算法,局部使用复制算法。

    4. 可预测的停顿。G1会建立可预测的停顿时间模型。

 

    G1与传统收集器不同的地方:

    G1将java堆分为许多个大小相同的独立区域(Region),新生代和老年代之间不再是物理隔离的,它们都是一部分Region的集合。

 

    G1为什么能建立可预测的停顿时间模型?

      G1可以有计划地避免在Java堆的进行全区域的垃圾收集,通过跟踪各个Region获得其收集价值大小(回收所获得的空间大小以及回收所需要的时间的经验值),在后台维护一个优先列表

    每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来),这就保证了在有限的时间内可以获取尽可能高的收集效率。

 

    G1如何解决一个对象被不同区域引用的问题?

      不止G1要解决这个问题,其他收集器也要解决这个问题,其他收集器需要解决新生代与老年代之间的对象引用问题。都是通过使用Rememed Set来避免全堆扫描。G1的每个Region都有一

    个与之对应的Remembered Set,虚拟机发现程序在对引用类型的数据进行读写操作时,会产生一个Write Barrier暂时中断写操作,检查引用类型引用的对象是否处于不同的Region之中,如果是,就

    通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在Gc根节点枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

 

    关于OopMap 和 Remembered Set

      总的来说:OopMap用于枚举根节点,Remembered Set 用来作可达性分析。

      OopMap避免了全栈扫描,Remembered Set避免了全堆扫描。

      新生代 GC(发生得非常频繁)。一般来说, GC过程是这样的:首先枚举根节点。根节点有可能在新生代中,也有可能在老年代中。这里由于我们只想收集新生代(换句话说,不想收集老年代),

    所以没有必要对位于老年代的 GC Roots 做全面的可达性分析。但问题是,确实可能存在位于老年代的某个 GC Root,它引用了新生代的某个对象,这个对象你是不能清除的。那怎么办呢?

      与OopMap一样,用空间换时间,用一个数据结构保存这种引用信息,这样在只需要再新生代上利用这两个东西就能完成可达性的分析。RememberedSet记录的是新生代的对象被老年代引用的关系。

    所以“新生代的 GC Roots ” + “ RememberedSet 存储的内容”,才是新生代收集时真正的 GC Roots 。

    参考:https://dsxwjhf.iteye.com/blog/2201685

                    https://blog.csdn.net/ifleetingtime/article/details/78934379

    

    G1的运作过程(不计维护Remembered Set)

    1. 初始标记(Initial Marking)           

      仅标记一下GC Roots能直接关联到的对象;

         且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;
         需要"Stop The World",但速度很快;


    2. 并发标记(Concurrent Marking)
       进行GC Roots Tracing
       耗时较长,但与用户线程并发执行;
  
    3. 最终标记(Final Marking)
       为了修正并发标记期间变动的对象的标记记录
       上一阶段对象的变化记录在线程的Remembered Set Log;
       这里把Remembered Set Log合并到Remembered Set中;
         需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
    采用多线程并行执行来提升效率;
         
  4. 筛选回收(Live Data Counting and Evacuation)
          首先排序各个Region的回收价值和成本;
          然后根据用户期望的GC停顿时间来制定回收计划;
          最后按计划回收一些价值高的Region中垃圾对象;
          回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;

 

(二)GC日志

      https://blog.csdn.net/xiaodu93/article/details/61926114

 

posted @ 2018-12-06 22:06  1码归1码  阅读(117)  评论(0编辑  收藏  举报