Java 各个GC的比较和选择

您所在的位置:网站首页 gc工作原理 Java 各个GC的比较和选择

Java 各个GC的比较和选择

2023-03-15 00:18| 来源: 网络整理| 查看: 265

前言

面试的时候提及了各个GC不同的选择,虽然以前了解过,但是还没有真正地总结过。小记一下。大概内容分以下一些步骤:

可达性分析 方法区的回收 垃圾收集算法 Stop the world & safe point 不同收集器的对比 可达性分析 引用计算算法也是判断对象是否游离的一种算法,但其无法解决对象间循环引用的问题。 可达性分析算法

从一系列的被称为GC Root对象开始,通过对象引用向下搜索,遍历引用链。不被遍历到的对象会被标记为游离对象,会在清理的时候被清理掉。GC Root对象包括以下:

虚拟机栈的对象 方法区中常量引用的对象 方法区类静态属性引用的对象 本地方法栈 Native 方法引用的对象

PS: 可达性分析时,不可达的对象非删除不可?

事实上并不是,被发现不可达的对象会经历是否执行finalize,假如在finalize方法中自我救赎,重新建立引用,则对象不会被删除。但finalize方法虚拟机只会对同一对象执行一次,是不可靠的方法。大部分程序都不会选择重写这个方法。 方法区的回收

方法区的回收效率是很低的,因为里面保存的大量都是类对象和类的静态变量,这些很少会被回收。一般回收的对象只有两种:废弃的常量和没用的类

废弃的常量

指,假设常量存在在一个 "abc" 字符串,但整个 JVM 中都没有引用这个字面常量,内存回收就会回收这个常量对象。

没用的类。同时符合以下三个条件会判断为:没用的类 类的所有实例都被回收了 加载该类的ClassLoader被回收了 类的 Class 对象没有被任何引用

PS: 大量使用反射、动态代理、CGLib、ASM 等字节码框架、动态生成 JPS、OSGi等自定义 ClassLoader 一般需要具备类卸载功能,保证永久代不溢出。

垃圾收集算法 标记-清除

最基础的收集算法。

优点:简单,直接 缺点:效率不高,产生碎片空间。 复制算法

利用双倍空间,消除上述的问题。适合朝生夕死的对象

优点:简单、高效 缺点:使用双倍空间 标记-整理 克服上两者的缺点。适合稳定的老年代 优点:没碎片空间,不浪费内存 缺点:处理时间长,复杂 分代收集

根据对象存活的特点选择不同的算法。新生代采用复制,老年代使用标记清除、或标记整理算法。

Stop the world & safe point

由于 GC 过程中需要考虑一致性的问题,防止因为对象关系的变动,在进行可达性分析时存在漏判或误判,JVM 在 GC 进行的过程中暂停 Java 所有的执行线程。称为 Stop the world。

如果要触发一次 GC ,那么 JVM 中所有的 Java 线程都必须到达 GC safe point。JVM 只会在特点的位置放置 safe point,譬如:

内存分配的地方 长时间执行区块结束的时刻(方法调用,循环跳转等)

safe point 还需要考虑一个问题,如何让线程在 safe point 的位置挂起?在设计上有两种方案:抢占式中断和主动式中断

抢占式。GC 先把所有线程全部中断,如果发现有不存在安全点的,恢复线程,直至它到达安全点。现在没有虚拟机这么做了。 主动式。GC 为所有线程设置中断标记,线程在"合适的时候"(通常就是Safe point)去轮询标记,发现为真时就自动挂起自己。

假如线程一直得不到 cpu 资源,由于饥饿无法到达safe point,改如何处理?

JVM 定义了 Safe Region (安全区域)的概念,扩展了 safe point。线程进入 Safe Region,表示 GC 可以随时对该线程进行 GC 操作。当线程要离开 Safe Region 时,会先查看系统是否完成了根节点枚举,没有的话就等待其完成再离开。

PS:整理思路下来,Stop the world其实不一定真的是需要严格挂起所有的用户线程,有点用户线程假如在安全区域里,可能还能活动一下。当然,鸡蛋里挑骨头是没什么必要的。

不同收集器的对比

没有任何一种收集器是放之皆准的。

Serial/Serial Old 收集器

最基本,最简答的收集器。单线程,主要工作原理,在 JVM 需要 GC 时,暂停所有的工作线程,对新生代进行复制 GC 清除,对老年代进行 标记-整理 清除。

优点:简单高效,适合单核CPU,Client 模式下的程序 缺点:Stop the world明显,不适合服务器使用。 ParNew 收集器

Serial 收集器的多线程版本,暂时没有很创新的地方,但是可以与 CMS 收集器共同协作,因此被青睐。

优点:在多 CPU 的环境下会比 Serial 收集器强 Parallel Scavenge 收集器

这个收集器的目标,是达到一个可控制的吞吐量。所谓吞吐量,就是 CPU 运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码的时间/(运行用户代码时间 + 垃圾收集器时间),eg: 虚拟机一共运行100分钟,其中垃圾收集划掉1分钟,那吞吐量就是99%

控制吞吐量大小的两个参数:

XX:MaxGCPauseMilis gc暂停最长时间。时间越短,吞吐量越小 XX:GCTimeRatio 直接设置吞吐量 Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。Parallel Scavenge 收集器是一个优秀的年轻代收集器,但只能与Serial Old合作,于是有了 Parallel Old 与其对接。其组合就是一个吞吐量优先的配置组合,适合一些 CPU 资源比较敏感的应用。

CMS 收集器

Concurrent Mark Sweep 收集器是一种以获取最短回收停顿时间为目标的收集器。关注服务端的响应速度,希望系统停顿时间短,CMS 收集器会非常符合这类应用。过程分4个步骤

初始标记(CMS inital mark) 并发标记(CMS concurrent mark) 重新标记(CMS remark) 并发清除(CMS concurrent sweep)

以下有几个特点:

初始标记、重新标记都是需要Stop the world,不过时间都比较短。 并发标记和并发清除是消耗时间最长的,并且会和用户线程一起进行。 并发标记就是从 CG Root 进行 Tracing 的过程,重新标记阶段是为了修正并发标记期间,用户程序继续运作导致标记产生变动的那一部分对象的标记记录。 优点: 正在的 GC 线程和用户线程共同执行,Stop the world 比较低,。

缺点:

CPU 资源敏感,会占用一部分的CPU资源导致用户线程停顿,总吞吐量会下降。一般比较适合多核CPU主机。 无法处理浮动垃圾。由于 CMS 并发清理,所以用户那个阶段产生的对象会无法在当前GC中清除,只能留到下一次GC再清理。那就是需要预留足够的内存空间给用户线程使用,所以CMS线程不能像其他收集器,等待年老代满了再进行收集,要预留空间提供并发收集时的程序使用。-XX:CMSInitiatingOccupancyFraction=70 这个参数是用于控制比率的,老年代的空间达到70%时,激活CMS GC。但假如设置太高,则会导致 Concurrent Mode Failure,GC 会采用后备方案,使用 Serial Old进行。 由于采取标记-清除算法,会存在空间碎片,可能需要引起 Full GC进行解决。

PS: Full GC 不等于 CMS GC。

G1 收集器

G1 收集器是一个比较先进的收集器。下面详细介绍一下。

堆结构

G1 会把一整块的堆空间,划分为固定内存的 region,大小从1-32Mb不等。

内存分配

region 会被分为 Eden、Survivor和old,这只是一个标签。对 region 的回收是并行的,其他线程照常工作。

Young GC

存活的对象从eden被转移到一个或多个survivor区,年龄达到一定阈值,就上升到 old 区

老年代GC 初始标记 (STW,原理和CMS的基本一致) 根分区扫描 并发标记(在这个堆中查找存活对象。这个阶段可能被YGC打断) 重新标记(STW,完成堆内存中存活对象的标记,使用一种SATB起始快照的算法) 清理阶段。 G1特点 并行和并发 分代收集 空间整合(标记-整理算法) 可预测的停顿(允许程序通过参数约定垃圾收集的时间) G1适合那些机器 和CMS同理,对响应时间要求高,吞吐量要求不太严格的 多核CPU,大内存JVM应用,Oracle 表示 G1 适用于堆大小为6GB以上的服务器应用程序。 引用 《深入理解Java虚拟机》第三章 垃圾收集器与内存分配策略 作者:周志明 可能是最全面的 Java G1学习笔记 Oracle 提议将 G1 作为 Java 9 的默认垃圾收集器


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3