Java的垃圾回收器有哪些

113次阅读
没有评论

共计 4377 个字符,预计需要花费 11 分钟才能阅读完成。

这篇文章主要讲解了“Java 的垃圾回收器有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着丸趣 TV 小编的思路慢慢深入,一起来研究和学习“Java 的垃圾回收器有哪些”吧!

我们先回顾一下主流 Java 的垃圾回收器(HotSpot JVM)。本文是针对堆的垃圾回收展开讨论的。堆被分解为较小的三个部分。具体分为:新生代、老年代、持久代。

绝大部分新生成的对象都放在 Eden 区,当 Eden 区将满,JVM 会因申请不到内存,而触发 Young GC , 进行 Eden 区 + 有对象的 Survivor 区 (设为 S0 区) 垃圾回收,把存活的对象用复制算法拷贝到一个空的 Survivor(S1)中,此时 Eden 区被清空,另外一个 Survivor S0 也为空。下次触发 Young GC 回收 Eden+S0,将存活对象拷贝到 S1 中。新生代垃圾回收简单、粗暴、高效。

若发现 Survivor 区满了,则将这些对象拷贝到 old 区或者 Survivor 没满但某些对象足够 Old, 也拷贝到 Old 区(每次 Young GC 都会使 Survivor 区存活对象值 +1,直到阈值)。Old 区也会进行垃圾收集(Young GC), 发生一次 Major GC 至少伴随一次 Young GC,一般比 Young GC 慢十倍以上。

JVM 在 Old 区申请不到内存,会进行 Full GC。Old 区使用一般采用 Concurrent-Mark–Sweep 策略回收内存。总结:Java 垃圾回收器是一种“自适应的、分代的、停止—复制、标记 - 清扫”式的垃圾回收器 缺点:

GC 过程中会出现 STW(Stop-The-World),若 Old 区对象太多,STW 耗费大量时间。

CMS 收集器对 CPU 资源很敏感。

CMS 收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次 Full GC 的产生。

CMS 导致内存碎片问题

# 比较

Serial 收集器
Serial 收集器是 JAVA 虚拟机中最基本、历史最悠久的收集器,在 JDK 1.3.1 之前是 JAVA 虚拟机新生代收集的唯一选择。Serial 收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个 CPU 或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Serial 收集器到 JDK1.7 为止,它依然是 JAVA 虚拟机运行在 Client 模式下的默认新生代收集器。它也有着优于其他收集器的地方:简单而高效(与其他收集器的单线程比),对于限定单个 CPU 的环境来说,Serial 收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial 收集器对于运行在 Client 模式下的虚拟机来说是一个很好的选择。
PS:开启 Serial 收集器的方式 -XX:+UseSerialGC 如:Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+UseSerialGC 的是 Serial 收集器,Xms30m -Xmx30m 指定了 JAVA 虚拟机的固定大小为 30M,-Xmn10m 指 JAVA 新生代的空间为 10M。

Parallel(并行)收集器
这是 JVM 的缺省收集器。就像它的名字,其最大的优点是使用多个线程来通过扫描并压缩堆。串行收集器在 GC 时会停止其他所有工作线程(stop-the-world),CPU 利用率是最高的,所以适用于要求高吞吐量(throughput)的应用,但停顿时间(pause time)会比较长,所以对 web 应用来说就不适合,因为这意味着用户等待时间会加长。而并行收集器可以理解是多线程串行收集,在串行收集基础上采用多线程方式进行 GC,很好的弥补了串行收集的不足,可以大幅缩短停顿时间(如下图表示的停顿时长高度,并发比并行要短),因此对于空间不大的区域(如 young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。图 1.Serial 收集器与 Parallel/ Throughput(并行)收集器的比较

CMS 收集器
CMS(Concurrent Mark Sweep)收集器是基于“标记 - 清除”算法实现的,它使用多线程的算法去扫描堆(标记)并对发现的未使用的对象进行回收(清除)。整个过程分为 6 个步骤,包括:初始标记(CMS initial mark)

并发标记(CMS concurrent mark)

并发预清理(CMS-concurrent-preclean)

重新标记(CMS remark)

并发清除(CMS concurrent sweep)

并发重置(CMS-concurrent-reset)

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。
初始标记仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快
并发标记阶段就是进行 GC Roots Tracing 的过程
重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。其他动作都是并发的。

需要注意的是,CMS 收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次 Full GC 的产生。由于 CMS 并发清理阶段用户线程还在运行着,伴随程序的运行自然还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS 无法在本次收集中处理掉它们,只好留待下一次 GC 时再将其清理掉。这一部分垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即还需要预留足够的内存空间给用户线程使用,因此 CMS 收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。在默认设置下,CMS 收集器在老年代使用了 68% 的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数 -XX:CMSInitiatingOccupancyFraction 的值来提高触发百分比,以便降低内存回收次数以获取更好的性能。要是 CMS 运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数 -XX:CMSInitiatingOccupancyFraction 设置得太高将会很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
还有一个缺点,CMS 是一款基于“标记 - 清除”算法实现的收集器,这意味着收集结束时会产生大量空间碎片。空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次 Full GC。为了解决这个问题,CMS 收集器提供了一个 -XX:+UseCMSCompactAtFullCollection 开关参数,用于在“享受”完 Full GC 服务之后额外免费附送一个碎片整理过程,内存整理的过程是无法并发的。空间碎片问题没有了,但停顿时间不得不变长了。虚拟机设计者们还提供了另外一个参数 -XX: CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的 Full GC 后,跟着来一次带压缩的。
该算法与并行收集器的另一个缺点是吞吐量的它使用更多的 CPU,为了使应用程序提供更好的体验,通过使用多个线程来执行扫描和收集。这种情况长时间的运行会使应用程序停顿下来,可以使用提高空间来换取高效的运行。但是,这种算法的使用不是默认的。您必须指定 XX:+ USeParNewGC 来使用它。如果你可以提供更多的 CPU 资源的话以避免应用程序暂停,那么你可以使用 CMS 收集器。假设你的堆的大小小于 4 Gb 你必须分配大于 4 GB 的资源。

G1 收集器
G1 垃圾收集器在 JDK7 update 4 之后对大于 4G 的堆有了更好的支持,G1 是一个针对多处理器大容量内存的服务器端的垃圾收集器,其目标是在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。G1 在执行一些 Java 堆空间中的全区域操作(如:全局标记)时是和应用程序线程并发进行的,因此减少了 Java 堆空间的中断比例。(译者注:可简单理解为减少了 Stop-the-World 的时间比例)。
它与前面的 CMS 收集器相比有两个显著的改进:一是 G1 收集器是基于“标记 - 整理”算法实现的收集器,也就是说它不会产生空间碎片,这对于长时间运行的应用系统来说非常重要。二是它可以非常精确地控制停顿,既能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒,具备了一些实时 Java(RTSJ)的垃圾收集器的特征。
首先将 Java 堆空间划分为一些大小相等的区域(region),每个区域都是虚拟机中的一段连续内存空间。G1 通过执行并发的全局标记来确定整个 Java 堆空间中存活的对象。标记阶段完成后,G1 就知道哪些区域基本上是空闲的。在回收内存时优先回收这些区域,这样通常都会回收相当数量的内存。这就是为什么它叫做 Garbage-First 的原因。顾名思义 G1 关注某些区域的回收和整理,这些区域中的对象很有可能被完全回收。而且 G1 使用了一个暂停时间预测模型使得暂停时间控制在用户指定的暂停时间内,并根据用户指定的暂停时间来选择合适的区域回收内存。
G1 确定了可回收的区域后就是筛选回收(evacuation)阶段了。在此阶段将对象从一个或多个区域复制到单一区域,同时整理和释放内存。该阶段是在多个处理器上多个线程并行进行的,因此减少了暂停时间并提高了吞吐量。G1 在每一次的垃圾收集过程中都不断地减少碎片,并能够将暂停时间控制在一定范围内。这些已经是以前的垃圾收集器无法完成的了。比如:CMS 收集器并不做内存整理。ParallelOld 收集器只是对整个 Java 堆空间做整理,这样导致相当长的暂停时间。

感谢各位的阅读,以上就是“Java 的垃圾回收器有哪些”的内容了,经过本文的学习后,相信大家对 Java 的垃圾回收器有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是丸趣 TV,丸趣 TV 小编将为大家推送更多相关知识点的文章,欢迎关注!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计4377字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)