垃圾收集器

垃圾收集器,第1张

垃圾收集器分类 按线程数分

按线程数分(垃圾回收线程数):可以分为串行垃圾回收器和并行垃圾回收器。

串行

指的是在同一时间段内只允许有一个CPU用于执行垃圾回收 *** 作,此时工作线程被暂停,直至垃圾收集工作结束。在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发回收器。所以,串行回收默认被应用在客户端的Client模式下的JVM中在并发能力比较强的CPU上,并行回收器产生的停顿时间要短于串行回收器。

并行

串行回收相反,并行收集可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用了"STW"机制。

按工作模式分 并发式垃圾回收器

与应用程序线程交替工作,以尽可能减少应用程序的停顿时间

独占式垃圾回收器

一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。

按碎片处理方式分

 

压缩式垃圾回收器

会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片。

非压缩式的垃圾回收器

不进行压缩整理 *** 作

按工作的内存区间分

按工作的内存区间分,又可分年轻代垃圾回收器和老年代垃圾回收器。

评估GC的性能指标
  1. 吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间 = 程序的运行时间 + 内存回收的时间)

  2. 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例。

  3. 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。

  4. 收集频率:相对于应用程序的执行,收集 *** 作发生的频率。

  5. 内存占用:Java堆区所占的内存大小。

  6. 快速:一个对象从诞生到被回收所经历的时间。

垃圾回收器

 

Serial

回收区域:Serial 垃圾收集器是 JVM 运行在 Client 模式下默认的新生代垃圾收集器; 回收算法:Serial 是最基本垃圾收集器,使用复制算法,曾经是 JDK1.3.1 之前新生代唯一的垃圾收集器; 回收线程:Serial 只会使用一个 CPU 或一条线程去完成垃圾收集工作。没有线程交互的开销,可以获得最高的单线程垃圾收集效率; 并行串行:Serial 在进行垃圾收集的时,必须暂停其他所有的工作线程,直到垃圾收集结束(串行);

不会立马停止会有个safe point(安全点)处停止

 

ParNew

回收区域:ParNew 垃圾收集器是很多 JVM 运行在 Server 模式下新生代的默认垃圾收集器; 复回收算法 : ParNew 和 Serial 一样使用了复制算法; 回收线程 :ParNew 垃圾收集器其实是 Serial 收集器的多线程版本。收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。 并行串行:ParNew 和 Serial 一样垃圾收集的同时,必须暂停其他所有的工作线程(串行);

区别是就是做了些增强以便于配合CMS使用

ParallelScavenge

 

回收区域:Parallel Scavenge 收集器也是一个新生代垃圾收集器; 回收算法:Parallel Scavenge 收集器同样使用复制算法; 回收线程 :Parallel Scavenge 收集器也是一个多线程的垃圾收集器; 并行串行:Parallerl Scavenge 收集器收集垃圾的同时,必须暂停其他所有的工作线程(串行);

Parallel Scavenge 重点关注的是程序达到一个可控制的吞吐量(Thoughput,吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)),高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务;主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。

常用参数
  • -XX:SurvivorRatio

  • -XX:PreTenureSizeThreshold 大对象到底多大

  • -XX:MaxTenuringThreshold

  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同

  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

     

Serial Old

回收区域:JVM 运行在 Client 模式下,Serial Old 是 Serial 垃圾收集器对应的老年代收集器; 回收算法:Serial Old 使用标记-整理算法; 回收线程:Serial Old 与 Serial 一样是单线程收集器; 并行串行:Serial Old 在进行垃圾收集的时,必须暂停其他所有的工作线程,直到垃圾收集结束(串行);

Client 模式: Server 模式:JVM 运行在 Server 模式下,Serial Old 主要有两个用途:

  1. 在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用;

  2. 作为年老代中使用 CMS 收集器的后备垃圾收集方案。

Parallel Old垃圾回收器

回收区域:Parallel Old 收集器是 Parallel Scavenge 垃圾收集器对应的的老年代收集器; 回收算法:Parallel Old 收集器使用标记-整理算法; 回收线程:Parallel Old 收集器是多线程收集器; 并行串行:Parallerl Scavenge 收集器收集垃圾的同时,必须暂停其他所有的工作线程(串行);

CMS(import)

回收区域:CMS(Concurrent mark sweep)收集器是一种老年代垃圾收集器; 回收算法:和其他年老代回收器一样,CMS 使用标记-清除算法; 回收线程 :CMS 采用的是多线程的标记-清除算法; 并行串行:CMS最主要目标是获取最短垃圾回收停顿时间,最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验(串行)。

CMS的优缺点

 

  • 优点

    低停顿时间

    并发收集

  • 缺点

    1. 内存碎片(Memory Fragmentation):内存碎片过多时会给大对象分配带来麻烦,即会存在空间足够,但是连续的空间太小,这样的话就会提前触发Full GC。CMS解决内存碎片的办法使用:-XX:CMSFullGCsBeforeCompaction参数(默认为0 指的是经过多少次FGC才进行压缩)

    2. 浮动垃圾(Floating Garbage):可能出现“Concurrent Mode Failure”导致另一次Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或交叉运行,那么在并发标记阶段如何产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC的时释放这些之前未被回收的内存空间(浮动垃圾过多会导致分配对象时内存不足从而触发Full GC)。解决方法:降低触发CMS的阈值,–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

    3. 对CPU资源敏感:在并发阶段它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低

    CMS只要触发FGC,就会让SerialOld(单线程)进行老年代回收,这个过程如果内存比较大的话,就会产生很长的STW(几个小时)

     

cms垃圾回收器会进行四个阶段
  1. 初始标记:根节点枚举,枚举全部的GC Roots对象。此阶段会stw,但是耗时极短。

  2. 并发标记:从标记的gcroots节点开始根据引用遍历gc堆,访问到的对象说明是可达的,访问不到的对象说明是不可达的可以被GC。

  3. 重新标记:用户线程和CMS线程是并发的,可能会导致某些对象改变,因此重新标记改变的对象

  4. 并发清除:清理标记的垃圾

     

三色标记算法

在并发标记过程中,我们采用三色标记算法来标记对象。具体如下:

把每个对象,按照是否访问过标记成三种颜色:

  • 白色:还没有被垃圾回收器访问

  • 黑色:已经被垃圾回收器访问,并且这个对象的所有引用也都被垃圾回收器访问过

  • 灰色:已经被垃圾回收器访问,但是这个对象至少还有一个引用没有被垃圾回收器访问

标记过程:

 

  1. 在GC并发标记刚开始时,所以对象均为白色集合。

  2. 将所有GCRoots直接引用的对象标记为灰色集合。

  3. 判断若灰色集合中的对象不存在子引用,则将其放入黑色集合,若存在子引用对象,则将其所有的子引用对象放入灰色集合,当前对象放入黑色集合

  4. 按照步骤三,以此类推,直至灰色集合中的所有对象变成黑色后,本轮标记完成,且当前白色集合内的对象称为不可达对象,既垃圾对象。

问题:由于此过程是在和用户线程并发运行的情况下,对象的引用处于随时可变的情况下,那么就会造成多标和漏标的问题。

多标:浮动垃圾,本应该被标记为白色的对象,没有被标记,造成该对象可能不会被回收。

比如E对象在GC扫描D对象时,E还正在被D引用,那么此时E就被标记为灰色,此时业务逻辑的变化,D指向E的引用被置空了,这时候E以及后续子引用本应该被当成垃圾回收,但是此时E已经被标记为灰色,导致E对象以及其子对象没有被及时清理掉,变成了浮动垃圾,还有在并发标记开始后的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能会变为垃圾,这也算是浮动垃圾的一部分。

漏标:灰色对象指向白色对象的引用消失了,然后一个黑色的对象重新引用了白色对象。

比如:D对象引用E对象,E引用G,此时GC正好处于D已经变成黑色,E处于灰色,G是白色的情况下,此时因为业务逻辑的变化,E不引用G了,D对象引用了G,按照三色标记法看,黑色对象是已完成状态,不可能再去找子引用,所以G就不会变成灰色,这样就会造成白色对象此时正在被线程使用中,但是无法被标记成灰色或者白色,造成一个正在被使用的对象被错误回收。也就是说,漏标必须满足一下两个条件:

条件一:灰色对象 断开了 白色对象的引用;即灰色对象 原来成员变量的引用 发生了变化。 条件二:黑色对象 重新引用了 该白色对象;即黑色对象 成员变量增加了 新的引用。

解决方案:

CMS:Incremental Update算法

当一个白色对象被一个黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描。(破坏条件二)

G1:SATB(Snapshot At The Beginning)算法

当原来成员变量的引用发生变化之前,记录下原来的引用对象,既原始快照,当B和C之间的引用马上被断掉时,将这个引用记录下来,使GC依旧能够访问到,那样白色就不会漏标。(破坏条件一)

ZGC

有类似于G1 的Region,但是没有分代。 标志性的设计是染色指针ColoredPointers,染色指针有4TB 的内存限制,但是效率极高,它是一种将少量额外的信息存储在指针上的技术。它可以做到几乎整个收集过程全程可并发,短暂的STW 也只与GC Roots 大小相关而与堆空间内存大小无关,因此实现任何堆空间STW 的时间小于十毫秒的目标。

Shenandoah

第一款非Oracle 公司开发的垃圾回收器,有类似于G1 的Region,但是没有分代。也用到了染色指针ColoredPointers。效率没有ZGC 高,大概几十毫秒的目标。

Epslion

这个垃圾回收器不能进行垃圾回收,是一个“不干活”的垃圾回收器,由RedHat 推出,它还要负责堆的管理与布局、对象的分配、与解释器的协作、与编译器的协作、与监控子系统协作等职责,主要用于需要剥离垃圾收集器影响的性能测试和压力测试。

G1

回收区域:G1(Garbage First)收集器再物理上已经不分代了,但在逻辑上还是分代,属于混合式收集器; 回收算法:G1 使用标记-整理算法; 回收线程 :G1 采用的是多线程的标记-整理算法; 并行串行:G1采用的是串行。 优 点: 基于 “标记-整理” 算法,收集后不会产生内存碎片。可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

因为G1垃圾回收器是物理上不分代,为了讲明白这个改建。首先介绍一下内存模型:

  • G1 收集器不采用传统的新生代和老年代物理隔离的布局方式,仅在逻辑上划分新生代和老年代,将整个堆内存划分为2048个大小相等的独立内存块Region,每个Region是逻辑连续的一段内存,具体大小根据堆的实际大小而定,整体被控制在 1M - 32M 之间,且为2的N次幂(1M、2M、4M、8M、16M和32M),并使用不同的Region来表示新生代和老年代,G1不再要求相同类型的 Region 在物理内存上相邻,而是通过Region的动态分配方式实现逻辑上的连续。

  • G1收集器通过跟踪Region中的垃圾堆积情况,每次根据设置的垃圾回收时间,回收优先级最高的区域,避免整个新生代或整个老年代的垃圾回收,使得STW的时间更短、更可控,同时在有限的时间内可以获得最高的回收效率。

  • 通过区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。

1. Region

G1 垃圾收集器将堆内存划分为若干个 Region,每个 Region 分区只能是一种角色,Eden区、S区、O区的其中一个,空白区域代表的是未分配的内存。还有个特殊的区域H区(Humongous),专门用于存放大对象,如果一个对象的大小超过Region容量的50%以上,G1 就认为这是个大对象。在其他垃圾收集器中,这些大对象默认会被分配在老年代,但如果它是一个短期存活的大对象,放入老年代就会对垃圾收集器造成负面影响,触发老年代频繁GC。为了解决这个问题,G1划分了一个H区专门存放大对象,如果一个H区装不下大对象,那么G1会寻找连续的H分区来存储,如果寻找不到连续的H区的话,就不得不触发 Full GC 了。

 

2. Remember Set

在串行和并行收集器中,GC时是通过整堆扫描来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,为每个分区各自分配了一个 RSet(Remembered Set),记录了其它 Region 对当前 Region 的引用情况,这样就带来一个极大的好处:回收某个Region时,不需要执行全堆扫描,只需扫描它的 RSet 就可以找到外部引用,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况,而这些引用就是 initial mark 的根之一。 事实上,并非所有的引用都需要记录在RSet中,如果引用源是本分区的对象,那么就不需要记录在 RSet 中;同时 G1 每次 GC 时,所有的新生代都会被扫描,因此引用源是年轻代的对象,也不需要在RSet中记录;所以最终只需要记录老年代到新生代之间的引用即可。

3. Card Table

在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out(我引用了谁),在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。

但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in(谁引用了我)来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。

需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

 

4. Collect Set

Collect Set(CSet)是指,在 Evacuation 阶段,由G1垃圾回收器选择的待回收的Region集合,在任意一次收集器中,CSet 所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。G1 的软实时性就是通过CSet的选择来实现的,对应于算法的两种模式 fully-young generational mode 和 partially-young mode,CSet的选择可以分成两种:

  1. fully-young generational mode:也称young GC,该模式下CSet将只包含 young region,G1通过调整新生代的 region 的数量来匹配软实时的目标;

  2. partially-young mode:也称 Mixed GC,该模式会选择所有的 young region,并且选择一部分的 old region,old region 的选择将依据在Marking cycle phase中对存活对象的计数,筛选出回收收益最高的分区添加到CSet中(存活对象最少的Region进行回收)

    候选老年代分区的CSet准入条件,可以通过活跃度阈值 -XX:G1MixedGCLiveThresholdPercent(默认85%) 进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比 -XX:G1OldCSetRegionThresholdPercent(默认10%) 设置数量上限。

由上述可知,G1的收集都是根据CSet进行 *** 作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件。

GC常用参数
  • -Xmn -Xms -Xmx –Xss 年轻代最小堆最大堆栈空间

  • -XX:+UseTLAB 使用TLAB,默认打开

  • -XX:+PrintTLAB 打印TLAB 的使用情况

  • -XX:TLABSize 设置TLAB 大小

  • -XX:+DisableExplicitGC 启用用于禁用对的调用处理的选项System.gc()

  • -XX:+PrintGC 查看GC 基本信息

  • -XX:+PrintGCDetails 查看GC 详细信息

  • -XX:+PrintHeapAtGC 每次一次GC 后,都打印堆信息

  • -XX:+PrintGCTimeStamps 启用在每个GC 上打印时间戳的功能

  • -XX:+PrintGCApplicationConcurrentTime 打印应用程序时间(低)

  • -XX:+PrintGCApplicationStoppedTime 打印暂停时长(低)

  • -XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用(重要性低)

  • -verbose:class 类加载详细过程

  • -XX:+PrintVMOptions 可在程序运行时,打印虚拟机接受到的命令行显示参数

  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 打印所有的JVM 参数、查看所有JVM 参数启动的初始值(必须会用)

  • -XX:MaxTenuringThreshold 升代年龄,最大值15, 并行(吞吐量)收集器的默认值为15,而CMS 收集器的默认值为6。

Parallel 常用参数
  • -XX:SurvivorRatio 设置伊甸园空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为8

  • -XX:PreTenureSizeThreshold 大对象到底多大,大于这个值的参数直接在老年代分配

  • -XX:MaxTenuringThreshold 升代年龄,最大值15, 并行(吞吐量)收集器的默认值为15,而CMS 收集器的默认值为6。

  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU 核数相同

  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS 常用参数
  • -XX:+UseConcMarkSweepGC 启用CMS 垃圾回收器

  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU 核数相同

  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS 收集,默认是68%(近似值),如果频繁发生SerialOld 卡顿,应该调小,(频繁CMS 回收)

  • -XX:+UseCMSCompactAtFullCollection 在FGC 时进行压缩

  • -XX:CMSFullGCsBeforeCompaction 多少次FGC 之后进行压缩

  • -XX:+CMSClassUnloadingEnabled 使用并发标记扫描(CMS)垃圾收集器时,启用类卸载。默认情况下启用此选项。

  • -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm 回收,JDK 8 中不推荐使用此选项,不能替代。

  • -XX:GCTimeRatio 设置GC 时间占用程序运行时间的百分比(不推荐使用)

  • -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代

G1 常用参数
  • -XX:+UseG1GC 启用CMS 垃圾收集器

  • -XX:MaxGCPauseMillis 设置最大GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且JVM 将尽最大的努力(G1 会尝试调整Young 区的块数来)来实现它。默认情况下,没有最大暂停时间值。

  • -XX:GCPauseIntervalMillis GC 的间隔时间

  • -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size 增加,垃圾的存活时间更长,GC 间隔更长,但每次GC 的时间也会更长

  • -XX:G1NewSizePercent 新生代最小比例,默认为5%

  • -XX:G1MaxNewSizePercent 新生代最大比例,默认为60%

  • -XX:GCTimeRatioGC 时间建议比例,G1 会根据这个值调整堆空间

  • -XX:ConcGCThreads 线程数量

  • -XX:InitiatingHeapOccupancyPercent 启动G1 的堆空间占用比例,根据整个堆的占用而触发并发GC 周期

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/883856.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-14
下一篇 2022-05-14

发表评论

登录后才能评论

评论列表(0条)

保存