• JVM学习02-GC算法与种类


    1. GC 简单介绍

      GC(Garbage Collection) 是垃圾收集的简写,GC机制是java中一个比較重要的概念。java的内存管理提供了内存的分配和释放。内存处理是程序编写人员非常easy出错的地方。忘记或错误的内存回收非常easy导致系统的不稳定,甚至瘫痪。

    java的GC机制能够非常好的检測对象是否超过作用域而能够达到回收的要求,从而实现自己主动回收垃圾对象的释放内存的目的。


       事实上早在非常久曾经(1960)就已经有了GC的概念,仅仅是java借用这个优秀的思想,在java内存模型中。GC的工作区域主要是在堆区域和方法区。大部分都是在堆区域中。

    2. GC 算法

    2.1 引用计数法

      首先呢,java并没有採用引用计数法作为GC算法,因为它有明显的缺陷。


      引用计数法实现比較简单:对于一个对象A来说。仅仅要有不论什么一个对象或者引用指向了A。那么A的计数器就加1。当引用失效时。引用计数器就减1,最后仅仅要对象A的引用计数器的值为0,那么A就不可能再被使用,就可被视为垃圾。


    这里写图片描写叙述
      引用计数法存在明显的缺陷,第一因为不断引用和去除引用伴随着加减法。影响性能。第二最大是问题就是非常难解决循环引用的问题。


    这里写图片描写叙述
    这样就会存在非常多的对象无法被回收,所以JVM压根没有使用引用计数法作为GC算法。

    2.2 标记-清除

      标记清除(Mark-Sweep)算法是现代垃圾回收算法的基础。顾名思义。标记清除算法将垃圾清除分为两个阶段:标记和清除

    标记:通过根节点。标记全部从根节点開始的可达对象。因此,未被标记的对象就是未被引用即不可达的对象。
    清除:清除全部未被标记的对象。


    这里写图片描写叙述

    2.3 标记-压缩

      标记压缩(Mark-Compact)算法适用于存活对象比較多的场合,比方老年代。它在标记-清除算法基础上做了一些优化,标记压缩算法和标记清除算法一样。首先通过根节点标记可达的对象。然后还将全部可达的对象(存活的对象)压缩到内存一端。然后清理边界外全部空间。


    标记:通过根节点标记可达的对象。
    压缩:将存活的对象整理到内存一端。清理边界外全部空间。
    这里写图片描写叙述

    2.4 复制算法

    与标记清除算法相比,复制(Copying)算法相对是一个比較高效的算法,因为涉及到存活对象的赋值,所以复制算法不适合存活对象比較多的场合(如不适合老年代)。复制算法的思想大致例如以下:将原有的内存分为两块空间,每次仅仅使用当中一块,在垃圾回收的时候,将正在使用内存中存活的对象拷贝到未使用的内存块中,之后清除正在使用内存中的全部对象,然后交换两个内存的角色完毕垃圾回收。
    这里写图片描写叙述

    2.5 增量算法

    另一种算法是增量算法(直接摘抄了):增量算法的基本思想是,假设一次性将全部的垃圾进行处理,须要造成系统长时间的停顿,那么就能够让垃圾收集线程和应用程序线程交替运行。每次。垃圾收集线程仅仅收集一小片区域的内存空间,接着切换到应用程序线程。

    依次重复,直到垃圾收集完毕。使用这样的方式,因为在垃圾回收过程中,间断性地还运行了应用程序代码。所以能减少系统的停顿时间。可是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

    2.6 总结

    1. 首先是引用计数法,这个缺陷比較明显。JVM也没有採用。不做讨论。

    2. 标记清除和标记压缩相比。前者产生大量不连续的内存碎片。

      后者尽管不产内存碎片,可是在前者的基础上还进行对象的压缩(整理),所以成本相对较高。

      且两者都会进行标记和清除。效率不是非常高。

    3. 复制算法尽管比較简单高效。且不会产生内存碎片。可是明显浪费了大量的内存空间

    3. 分代思想

      依据GC算法的总结能够知道,结合不同算法的有点才干规划出一套比較可行的方案,所谓没有最好仅仅有更好。
      前面JVM内存模型中有说到将JAVA堆分为新生代和老年代。然后又将新生代细分为eden space。survivor space(分为from和to或者s0和s1)。然后依据对象的存活周期将短命对象归为新生代,长命对象归为老年代。
      然后依据不同代的特点,在对象存活比較少的新生代採用复制算法。在老年代採用标记清除或标记压缩算法。
    这里写图片描写叙述
    图中。在进行垃圾回收的时候。存活对象怎样存放有非常多种可能。
    1. 左边绿色的箭头表示在垃圾回收的时候。第一种年龄比較大的对象会存放在老年代中;另外一种情况就是一些比較大的对象无法放到 survivor 空间中。那么此时大对象也被存放到老年代中,所以有的是也称老年代是一个担保空空。
    2. 左边红色和黄色的箭头就是进行正常的复制算法。垃圾回收结束就是右图的样子。
    3. 有的时候查看GC日志的时候,新生代的垃圾回收一般称为minor gc。老年代因为区域比較大且存活对象非常多。生命周期非常长,所以gc时候会比較长。通常称为full gc。

    4. 对象的可触及性

    对象的可触及性顾名思义就是从根节点能够标记的对象。大家普通情况会觉得对象要么是可触及的,要么是不可触及的,事实上中间还存在一个可复活性。


    1. 可触及性
     从根节点能够触及的对象
    2. 可复活性
    一旦全部引用被释放。对象进入可复活状态,因为在 finalize() 方法后对象可能变得可触及
    3. 不可触及性
    finalize() 方法之后,对象可能进入不可触及状态。不可触及的对象不能复活。然后进入可回收的状态。

    这里须要注意的一点是 finalize() 方法仅仅会被运行一次,所以某个对象不能无限又可复活性到达可触及性。

    并且 finalize() 方法的运行优先级非常低,何时出发GC并不确定。finalize()方法的调用也变得不确定。
    更具体參照:《finalize 总结》

    5. Stop-The-World (STW) 现象

      STW现象是java的一种全局暂停的现象。全部Java代码停止运行(Native代码能够运行,但不能与JVM交互)。产生这样的现象的原因多半是因为 GC 引起的。

    GC 为什么产生 STW 现象?这个以开 Party 打个比方:我们在开party 的时候会产生非常多的垃圾,那么此时假设有人来清理垃圾。我们他在清理的时候。我们又不断产生垃圾。那么房间永远打扫不干净,所以唯一的办法就是我们停止手中的事情,直到房间打扫干净后再进行活动。
      那么GC也是这个道理,当发生GC的时候,必定全部的工作线程会停止,那么此时就会产生java停顿的现象。
      当发生STW现象的时候。假设时间短还好,假设时间特别长甚至几十分钟,服务器就会长时间得不到响应,那么就会带来比較大的危害。解决方法能够使用主备机的切换吧。具体不是非常清楚,也不展开了。

    6. 垃圾回收器的种类

    6.1 新生代串行收集器

    串行收集器是一个古老而稳定,经过长时间考验的垃圾收集器。

    在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现能够超过并行回收器和并发回收器。

    可是有的时候会停顿非常长时间,且是线程独占的。
    新生代串行收集器採用了复制算法,当 JVM 在 Client 模式下运行时。它是默认的垃圾收集器。


    在 HotSpot 虚拟机中。使用 -XX:+UseSerialGC 參数能够指定使用新生代串行收集器和老年代串行收集器。此时老年代串行收集器採用的是标记-压缩算法
    这里写图片描写叙述

    [GC 0.844: [DefNew: 17472K->2176K(19648K), 0.0188339 secs] 17472K->2375K(63360K), 0.0189186 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

    6.2 老年代串行收集器

    老年代串行收集器和新生代串行收集器一样。老年代串行收集器採用的是标记-压缩算法,能够使用 -XX:+UseSerialGC 參数能够指定使用新生代串行收集器和老年代串行收集器。
    图解和新生代串行收集器一样。

     [Full GC 8.259: [Tenured: 43711K->40302K(43712K), 0.2960477 secs] 63350K->40302K(63360K), [Perm : 17836K->17836K(32768K)], 0.2961554 secs] [Times: user=0.28 sys=0.02, real=0.30 secs]

    6.3 并行收集器

    并行收集器工作在新生代,仅仅是将新生代串行收集器变成了多线程化了,它的回收策略、算法以及參数和串行回收器一样。
    新生代依然採用复制算法,老年代还是串行收集器(标记压缩算法)。GC依然是线程独占的。


    开启并行回收器能够使用參数 -XX:+UseParNewGC
    这里写图片描写叙述
    多线程并不意味着GC一定会非常快,且须要多核CPU的支持才会相对提高效率。
    能够使用 -XX:ParallelGCThreads 限制线程数量

    [GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

    6.4 新生代并行回收收集器

    与 ParNew 相似。新生代并行回收收集器也是使用复制算法的收集器。从表面上看,它和并行收集器一样都是多线程、独占式的收集器。

    可是。并行回收收集器有一个重要的特点:它非常关注系统的吞吐量


    使用 -XX:+UseParallelGC 设置 新生代使用Parallel收集器+ 老年代使用串行收集器
    使用 -XX:+UseParallelOldGC 设置 新生代和老年代都使用Parallel收集器
    这里写图片描写叙述

    [Full GC [PSYoungGen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03, real=0.30 secs]

    6.5 老年代并行回收收集器

    老年代的并行收集器和新生代并行收集器一样。所线程且关注系统吞吐量。採用标记压缩的算法。


    使用 -XX:+UseParallelOldGC 设置 新生代和老年代都使用Parallel收集器
    图解和清单和新生代并行收集器一样。
    这里新生代和老年代的并行收集器和使用下面參数启动:
    -XX:MaxGCPauseMills : 设置最大的停顿时间,单位是毫秒。

    GC会尽力保证回收的时间不超过吞吐量。
    -XX:GCTimeRatio : 设置吞吐量的大小n。GC时间比[ 1/(1+n) ]。默认值为99。即最大同意1%的时间用于垃圾回收。
    这两个參数本来就是矛盾的,假设将最大停顿时间设置越小,那么GC就会越频繁,从而减少整个系统的吞吐量。

    假设吞吐量设置越大,GC导致的停顿时间也会越长,所以有所矛盾。所以在实际中也能够採用自适应的GC调节策略,使用 -XX:+UseAdaptiveSizePolicy 能够打开自适应 GC 策略。在这样的模式下。新生代的大小、eden 和 survivor 的比例、晋升老年代的对象年龄等參数会被自己主动调整。以达到在堆大小、吞吐量和停顿时间之间的平衡点。

    在手工调优比較困难的场合,能够直接使用这样的自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完毕调优工作。

    6.6 CMS收集器

      CMS (Concurrent Mark Sweep)是并发标记清除的收集器,这里的并发表示能够和用户线程一起工作。且採用标记清除算法


      CMS 收集器针是老年代收集器,新生代採用 ParNew 并行收集器。
      能够使用 -XX:+UseConcMarkSweepGC 启动 CMS 收集器。
      CMS 工作大致可分为例如以下几个过程:
    1. 初始标记:GC 线程独占,对根能够直接关联到的对象进行标记,速度比較快。
    2. 并发标记:GC 线程和用户线程一起运行,对全部的对象进行标记。
    3. 又一次标记:因为上一步并发过程。用户线程可能还会生产出垃圾,所以GC 线程独占。在正式清理前又一次做一次标记。
    4. 并发清除:GC 线程和用户线程一起运行。GC 回收垃圾对象。
    这里写图片描写叙述

    1.662: [GC [1 CMS-initial-mark: 28122K(49152K)] 29959K(63936K), 0.0046877 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    1.666: [CMS-concurrent-mark-start]
    1.699: [CMS-concurrent-mark: 0.033/0.033 secs] [Times: user=0.25 sys=0.00, real=0.03 secs] 
    1.699: [CMS-concurrent-preclean-start]
    1.700: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    1.700: [GC[YG occupancy: 1837 K (14784 K)]1.700: [Rescan (parallel) , 0.0009330 secs]1.701: [weak refs processing, 0.0000180 secs] [1 CMS-remark: 28122K(49152K)] 29959K(63936K), 0.0010248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    1.702: [CMS-concurrent-sweep-start]
    1.739: [CMS-concurrent-sweep: 0.035/0.037 secs] [Times: user=0.11 sys=0.02, real=0.05 secs] 
    1.739: [CMS-concurrent-reset-start]
    1.741: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

    CMS尽管是并发收集器,可是事实上也会存在线程独占的情况导致全局停顿,仅仅是减少了停顿的时间罢了。因为用户线程的运行过程中,GC的时候还须要分CPU去做垃圾回收,这样就会大大减少总体系统的反应速速。在清理阶段。因为和用户线程并发运行,还会产生新的垃圾,导致清理不彻底。甚至还可能会存在产生的垃圾使得CMS来不及清理。让可使用内存的容量迅速减小。直到内存预留不够,引起 concurrent mode failure 错误。
    解决方式:
    第一能够通过 -XX:CMSInitiatingOccupancyFraction 的值来设置触发CMS收集器的阀值。

    默觉得68,即当老年代空间使用率达到68%的时候触发CMS回收。
    第二当引起concurrent mode failure 错误的时候,JVM就会启动备用回收器 **老年代串行回收器** 作为GC回收器。
    CMS收集器的一些參数:
    1. -XX:+UseCMSCompactAtFullCollection 參数将在进行一个Full GC之后进行一次内存压缩(整理),因为CMS採用的是标记清除算法。因为整理过程是线程独占的。所以可能引起的停顿时间较长。
    2. -XX:+CMSFullGCsBeforeCompaction 參数设置多少次Full GC后进行一次内存整理。


    3. -XX:ParallelCMSThreads 參数设置CMS线程的数量。

    6.7 G1收集器

    G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同一时候,还具备高吞吐量性能特征. 在Oracle JDK 7 update 4 及以上版本号中得到全然支持, 专为下面应用程序设计:
    1. 能够像CMS收集器一样,GC操作与应用的线程一起并发运行
    2. 紧凑的空暇内存区间且没有非常长的GC停顿时间.
    3. 须要可预測的GC暂停耗时.
    4. 不想牺牲太多吞吐量性能.
    5. 启动后不须要请求更大的Java堆.
    G1的长期目标是代替CMS(Concurrent Mark-Sweep Collector, 并发标记-清除). 因为特性的不同使G1成为比CMS更好的解决方式. 一个差别是,G1是一款压缩型的收集器,G1通过有效的压缩全然避免了对细微空暇内存空间的分配,不用依赖于regions。这不仅大大简化了收集器。并且还消除了潜在的内存碎片问题。

    除压缩以外,G1的垃圾收集停顿也比CMSeasy预计,也同意用户自己定义所希望的停顿參数(pause targets)。
    具体參考博文:《G1垃圾收集器入门》

    7. GC 相关的參数

    1. 与串行回收器相关的參数
    -XX:+UseSerialGC : 在新生代和老年代使用串行回收器。

    -XX:+SuivivorRatio : 设置 eden 区大小和 survivor 区大小的比例。8表示 两个Survivor:eden=2:8。即一个Survivor占年轻代的1/10。

    -XX:NewRatio : 新生代和老年代(不包括永久区)的比。4 表示 新生代:老年代=1:4,即年轻代占堆的1/5。

    -XX:+PretenureSizeThreshold : 设置大对象直接进入老年代的阈值。

    当对象的大小超过这个值时,将直接在老年代分配。

    -XX:MaxTenuringThreshold : 设置对象进入老年代的年龄的最大值。

    每一次 Minor GC 后,对象年龄就加 1。不论什么大于这个年龄的对象,一定会进入老年代。

    2. 与并行 GC 相关的參数
    -XX:+UseParNewGC : 在新生代使用并行收集器。

    -XX:+UseParallelOldGC : 老年代使用并行回收收集器。

    -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下能够和 CPU 数量相等。但在 CPU 数量比較多的情况下,设置相对较小的数值也是合理的。

    -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其它一些參数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。

    -XX:GCTimeRatio : 设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

    -XX:+UseAdaptiveSizePolicy : 打开自适应 GC 策略。

    在这样的模式下,新生代的大小,eden 和 survivor 的比例、晋升老年代的对象年龄等參数会被自己主动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。

    3. 与 CMS 回收器相关的參数
    -XX:+UseConcMarkSweepGC : 新生代使用并行收集器,老年代使用 CMS + 串行收集器(备用收集器)。

    -XX:+ParallelCMSThreads : 设定 CMS 的线程数量。

    -XX:+CMSInitiatingOccupancyFraction : 设置 CMS 收集器在老年代空间被使用多少后触发,默觉得 68%。

    -XX:+UseFullGCsBeforeCompaction : 设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。

    -XX:+CMSClassUnloadingEnabled : 同意对类元数据进行回收。

    -XX:+CMSParallelRemarkEndable : 启用并行重标记。

    -XX:CMSInitatingPermOccupancyFraction : 当永久区占用率达到这一百分比后。启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。

    -XX:UseCMSInitatingOccupancyOnly : 表示仅仅在到达阈值的时候,才进行 CMS 回收。

    -XX:+CMSIncrementalMode : 使用增量模式。比較适合单 CPU。

    4. 与 G1 回收器相关的參数
    -XX:+UseG1GC:使用 G1 回收器。

    -XX:+UnlockExperimentalVMOptions : 同意使用实验性參数。

    -XX:+MaxGCPauseMills : 设置最大垃圾收集停顿时间。

    -XX:+GCPauseIntervalMills : 设置停顿间隔时间。

    5. 其它參数
    -XX:+PrintGCDetails : 打开显示GC日志的开关。

    -XX:+DisableExplicitGC : 禁用显示 GC。

    -Xloggc:Xxx.log : 设置GC的log位置和名称。

    -XX:+HeapDumpOnOutOfMemoryError : 当堆内存移除出错的时候显示最后的GC日志。

    -Xmx 和 –Xms : 设置堆内存的最大同意值和最小值,如-Xmx32M -Xms32M

    -XX:PermSize 和 -XX:MaxPermSize : 设置永久代的最小初始值和最大同意值 如-XX:PermSize=64MB 和 -XX:MaxPermSize=256M。

    -XX:MaxPermSize缺省值和client/server选项相关,-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。

    -Xmn : 设置新生代大小 , 如 -Xmn16M

  • 相关阅读:
    android之字符串的一些转码
    android之界面一些操作
    日期的一些处理
    android数据的4种存储方式
    第四周学习情况
    第四周作业2
    第四周作业
    第三章学习情况
    第二章学习情况(2020.02.24-2020.03.01)
    个人情况介绍+《人月神话》读后感+本周学习情况(2020.02.17-2020.02.23)
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7233028.html
Copyright © 2020-2023  润新知