目录
一、标记-清除(Mark-Sweep)
二、复制(Copying):
三、标记-整理(Mark-Compact):
四、垃圾回收器
4.1串行收集器
4.2并行收集器
4.3 并发收集器/CMS收集器
五、Garbage Firest(G1)
5.1算法详解:
5.2 回收步骤:
JVM基本垃圾回收算法,按照基本回收策略分
标记-清除(Mark-Sweep)
一、标记-清除(Mark-Sweep)
https://www.html.cn/qa/other/22924.html
此算法执行分两阶段。
第一阶段 标记:遍历内存区域,对需要回收的对象打上标记。
第二阶段 清除:再次遍历内存,对已经标记过的内存进行回收。
此算法需要暂停整个应用
绿色、蓝色---代表存活对象
灰色---非存活对象
白色--未使用的内存
缺点:
效率问题:遍历了两次内存空间(第一次标记,第二次清除)。
空间问题:gc之后,会产生大量的内存碎片,随着gc次数增多,未使用的内存会被切割的越来越小。这些零碎的内存,足够小时,就不能存放连续的内存(如数组,数组是连续的内存空间),如数组10m,但是这一块内存只有1m,虽然这块内存未使用,但是也不能用来存放这个10m的数组。这种零碎的内存越来越多,则整体可以使用的内存越来越少。当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。
二、复制(Copying):
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。
垃圾回收时,遍历当前使用区域, 把正在使用中的对象复制到另外一个区域中。
此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。
当然,此算法的缺点也 是很明显的,就是需要两倍内存空间。
jvm执行YGC时,清理伊甸园区和两个存活区就使用的复制算法。
老年代默认使用的是 标记-清除 算法,不可以使用复制算法,使用标记-清除算法,内存都可以被回收掉,但是不能清除碎片内存。
在这两个算法之上,又出现了 标记-整理(Mark-Compact)算法,结合了两种算法的优点。
三、标记-整理(Mark-Compact):
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,
第一阶段从根节点开始标 记所有被引用对象(寻根判断),
第二阶段遍历整个堆,清除非存活的对象并且把存活对象“压缩”到堆的其中一 块,按顺序排放。
此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
FGC,清理老年代时,期望使用标记-整理算法,
四、垃圾回收器
最开始的垃圾回收器是 串行收集器,如果不配置,那就使用默认的。
垃圾回收器指的就是用几个线程区回收,垃圾回收过程如何控制,
4.1串行收集器
用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。
但是,也无法使用多处理 器的优势,所以此收集器适合单处理器机器。
当然,此收集器也可以用在小数据量(100M 左右)情况下的多处理器机器上。
可以使用-XX:+UseSerialGC 打开。
年轻代YGC
串行垃圾回收,如图一共4个cpu,YGC用一个线程来进行垃圾回收(寻根判断--复制算法),
YGC时只有一个线程进行垃圾收集,此时应用程序处于暂停状态,不能工作。
老年代FGC
串行收集,标记-整理算法,一个线程进行垃圾收集,只有一个线程干活,效率比较低。
串行收集这种效率比较低,基本上已经废弃了。如果没有单独配置垃圾回收策略,1.7及之前的JDK,默认用的就是这种垃圾回收器。
1.8JDK如果不配置垃圾回收器,默认是并行收集器。
4.2并行收集器
年轻代,执行YGC时,多个线程进行寻根判断,标记存活对象,多个线程进行通过复制算法进行垃圾回收,可以发挥多核cpu的优势,效率比较高。
但是老年代依然使用的串行收集,所以老年代还是比较慢。
对年轻代迕行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用 -XX:+UseParallelGC.打开。
并行收集器在 J2SE5.0 第六 6 更新上引入,在 Java SE6.0 中迕行了增强 --可以对年老代迕行并行收集。
如果年老代不使用并发收集的话,默认是使用单线程进行垃圾回收, 因此会制约扩展能力。使用-XX:+UseParallelOldGC 打开。
所以:
如果只配置 -XX:+UseParallelGC,只是单纯的并行年轻代。
打开-XX:+UseParallelOldGC,就可以同时并行老年代了
使用-XX:ParallelGCThreads=设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相 等。
并行收集器,年轻代并行收集、老年代并行收集时,都会暂停应用程序,如果内存足够大,暂停时间依然很长。
4.3 并发收集器/CMS收集器
并发收集器简称CMS收集器(Concurrent Mark Sweep),
CMS主要减少年老代的暂停时间,可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应 时间要求比较高的中、大规模应用。
使用-XX:+UseConcMarkSweepGC 打开。
其中“Concurrent”并发是指垃圾收集的线程和用户执行的线程是可以同时执行的。
年轻代使用复制算法,老年代使用标记-清除(不是标记-整理)。
CMS是基于“标记-清除”算法实现的,整个过程分为4个步骤:
1、初始标记(CMS initial mark)。
2、并发标记(CMS concurrent mark)。
3、重新标记(CMS remark)。
4、并发清除(CMS concurrent sweep)。
注意:“标记”是指将存活的对象和要回收的对象都给标记出来,而“清除”是指清除掉将要回收的对象。
初始标记、重新标记这两个步骤仍然需要暂停应用。
初始标记:初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。单线程标记,暂停应用程序,不使用多线程原因,
1、初始标记这个过长极短
2、如果是多线程标记,最后还要同步数据,消耗时间可能会更长
并发标记:并发标记阶段(这个阶段不会阻碍业务线程继续执行)就是进行GC Roots Tracing(从GC Roots开始找到它能引用的所有其它对象)的过程。
重新标记:重新标记阶段则是为了修正并发标记期间因用户程序继续动作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
CMS收集器的动作步骤如上图所示,在整个过程中耗时最长的并发标记和并发清除过程,收集器线程都可以与用户线程一起工作,因此,从总体上看,CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:并发收集、低停顿【注意:这里的停顿指的是停止用户线程】,Oracle公司的一些官方文档中也称之为并发低停顿收集器(Concurrent Low Pause Collector)。
年轻代-多个线程进行垃圾回收工作,暂停应用程序,但是暂停时间会比较短。
在每个老年代垃圾回收周期中,在收集初期并发收集器 会对整个应用进行简短的暂停,在 收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。
五、Garbage Firest(G1)
概述
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一,从设计目标看 G1 完全是为了大型应用而准备的。 同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。
G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。
支持很大的堆
高吞吐量
--支持多 CPU 和垃圾回收线程
--在主线程暂停的情冴下,使用并行收集
--在主线程运行的情冴下,使用并发收集
实时目标:可配置在 N 毫秒内最多只占用 M 毫秒的时间进行垃圾回收
当然 G1 要达到实时性的要求,相对传统的分代回收算法,在性能上会有一些损失。
开启选项:-XX:+UseG1GC
之前介绍的几组垃圾收集器组合,都有几个共同点:
1.年轻代、老年代是独立且连续的内存块;
2.年轻代收集使用单eden、双survivor进行复制算法;
3.老年代收集必须扫描整个老年代区域;
4.都是以尽可能少而快地执行GC为设计原则。
G1垃圾收集器也是以关注延迟为目标、服务器端应用的垃圾收集器,被HotSpot团队寄予取代CMS的使命,也是一个非常具有调优潜力的垃圾收集器。
虽然G1也有类似CMS的收集动作:初始标记、并发标记、重新标记、清除、转移回收,并且也以一个串行收集器做担保机制,但单纯地以类似前三种的过程描述显得并不是很妥当。
事实上,G1收集与以上三组收集器有很大不同:
1、G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
2、G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
3、G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
4、G1的收集都是STW(STW: Stop The World)的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
5.1算法详解:
G1 可谓博采众家之长,力求到达一种完美。他吸取了增量收集优点,把整个堆划分为一个一个等大 小的区域(region)。
内存的回收和划分都以 region 为单位;同时,他也吸取了 CMS 的特点,把 这个垃圾回收过程分为几个阶段,分散一个垃圾回收过程;而且,G1 也认同分代垃圾回收的思想, 认为不同对象的生命周期不同,可以采取不同收集方式,因此,它也支持分代的垃圾回收。
为了达到 对回收时间的可预计性,G1 在扫描了 region 以后,对其中的活跃对象的大小进行排序,首先会收集 那些活跃对象小的 region,以便快速回收空间(要复制的活跃对象少了),因为活跃对象小,里面 可以认为多数都是垃圾,所以这种方式被称为 Garbage First(G1)的垃圾回收算法,即:垃圾优先 的回收。
5.2 回收步骤:
初始标记(Initial Marking)
G1 对于每个 region 都保存了两个标识用的 bitmap,一个为 previous marking bitmap,一个为 next marking bitmap,bitmap 中包含了一个 bit 的地址信息来指向对象的起始点。
开始 Initial Marking 之前,首先并发的清空 next marking bitmap,然后停止所有应用线程,并扫 描标识出每个region中root可直接访问到的对象,将region中top的值放入next top at mark start (TAMS)中,之后恢复所有应用线程。
触发这个步骤执行的条件为:
G1定义了一个JVM Heap大小的百分比的阀值,称为h,另外还有一个 H,H的值为(1-h)*Heap Size, 目前这个 h 的值是固定的,后续 G1 也许会将其改为动态的,根据 jvm 的运行情况来动态的调整,在 分代方式下,G1 还定义了一个 u 以及 soft limit,soft limit 的值为 H-u*Heap Size,当 Heap 中使 用的内存超过了 soft limit 值时,就会在一次 clean up 执行完毕后在应用允许的 GC 暂停时间范围内 尽快的执行此步骤;
在 pure 方式下,G1 将 marking 与 clean up 组成一个环,以便 clean up 能充分的使用 marking 的信息,当 clean up 开始回收时,首先回收能够带来最多内存空间的 regions,当经过多次的 clean up,回收到没多少空间的 regions 时,G1 重新初始化一个新的 marking 与 clean up 构成的环。
并发标记(Concurrent Marking)
按照之前 Initial Marking 扫描到的对象进行遍历,以识别这些对象的下层对象的活跃状态,对于在 此期间应用线程并发修改的对象的以来关系则记录到 remembered set logs 中,新创建的对象则放 入比 top 值更高的地址区间中,这些新创建的对象默认状态即为活跃的,同时修改 top 值。
最终标记暂停(Final Marking Pause)
当应用线程的 remembered set logs 未满时,是不会放入 filled RS buffers 中的,在这样的情况下, 这些 remebered set logs 中记录的 card 的修改就会被更新了,因此需要这一步,这一步要做的就 是把应用线程中存在的 remembered set logs 的内容进行处理,并相应的修改 remembered sets, 这一步需要暂停应用,并行的运行。
存活对象计算及清除(Live Data Counting and Cleanup)
值得注意的是,在 G1 中,并不是说 Final Marking Pause 执行完了,就肯定执行 Cleanup 这步的, 由于这步需要暂停应用,G1 为了能够达到准实时的要求,需要根据用户指定的最大的 GC 造成的暂 停时间来合理的规划什么时候执行 Cleanup,另外还有几种情况也是会触发这个步骤的执行的:
G1 采用的是复制方法来进行收集,必须保证每次的”to space”的空间都是够的,因此 G1 采取的 策略是当已经使用的内存空间达到了 H 时,就执行 Cleanup 这个步骤;
对于 full-young 和partially-young 的分代模式的 G1 而言,则还有情况会触发 Cleanup 的执行,
full-young 模式下,G1 根据应用可接受的暂停时间、回收 young regions 需要消耗的时间来估算出 一个 yound regions 的数量值,当 JVM 中分配对象的 young regions 的数量达到此值时,Cleanup 就会执行;
partially-young 模式下,则会尽量频繁的在应用可接受的暂停时间范围内执行 Cleanup, 并最大限度的去执行 non-young regions 的Cleanup。