常见的垃圾收集器

gc

Posted by CHuiL on August 19, 2021

垃圾收集器选择

  • serial collector:-XX:+UseSerialGC;适合单处理器,小型数据场景的应用(小于100M)。
  • parallel collector: -XX:+UseParallelGC;并发线程执行gc,减少gc时间,提高吞吐量。算法与serial collector类似,不过他是并发执行。minor 和 major都是并发。适合多核中大型服务应用。
  • concurrent collector:CMS和G1。gc启动后,不会立即stw,而是与应用线程一起执行。只要在需要的时候在stw,以达到尽可能减少stw时间的目的,提高响应时间,不过吞吐量对应会降低。适合大型服务应用。

image image

吞吐量与响应时间
  • 吞吐量 = CPU在用户应用程序运行的时间 / (CPU在用户应用程序运行的时间 + CPU垃圾回收的时间)
  • FULL GC,串行垃圾回收会使用应用停顿,响应用户时间长

Serial GC

  • 它是最古老的垃圾收集器
  • 单线程
  • STW
  • 复制算法

ParNew GC

很明显是个新生代 GC 实现,它实际是 Serial GC 的多线程版本

CMS(Concurrent Mark Sweep) GC

  • 基于标记-清除算法,设计的目的是为了减少停顿时间
  • 标记-清除算法存在内存碎片化问题?长时间可能发生full gc
  • 强调了并发,会占用更多CPU资源,并和用户线程争抢
  • CMS 已经在 JDK 9 中被标记为废弃(deprecated)

Parallel GC

  • 它是 server 模式 JVM 的默认 GC 选择,也被称作是吞吐量优先的 GC
  • 它的算法和 Serial GC 比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。

在常见的服务器环境中更加高效。

G1 GC

  • 兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项
  • 相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。
  • 任然存在老年代的概念,内存结构并不是简单条带划分,而是类似棋盘的一个个region。Region之间是复制算法
  • 标记-整理算法,可以有效避免内存碎片。

image

对象实例收集算法

引用计数

为对象添加一个引用计数,用于记录对象被引用的情况。如果该值为0,表示对象可以被回收。java没有选择这种算法,因为一个基本难题,也就是很难处理循环引用的关系

可达性分析

将对象之间的关系看成一个图,选定活动的对象作为一个GC Roots,然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用链条,那么可以认为是可回收对象。一般将虚拟机栈,本地方法栈中正在引用的对象、静态属性引用的对象和常量,作为GC Roots。(堆外指向堆内的引用)

垃圾收集的算法有哪些?

复制(Copying)算法

新生代 GC,基本都是基于复制算法;将活着的对象复制到 to 区域,拷贝过程中将对象顺序放置,就可以避免内存碎片化。

这么做的代价是,既然要进行复制,既要提前预留内存空间,有一定的浪费;另外,对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,这个开销也不小,不管是内存占用或者时间开销。

标记 - 清除(Mark-Sweep)算法

首先进行标记工作,标识出所有要回收的对象,然后进行清除。

这么做除了标记、清除过程效率有限,另外就是不可避免的出现碎片化问题,这就导致其不适合特别大的堆;否则,一旦出现 Full GC,暂停时间可能根本无法接受。

标记 - 整理(Mark-Compact)

类似于标记 - 清除,但为避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。整理消耗很大。

java8默认的gc? 可用的gc有哪些?

查看jvm默认使用的gc,可以使用命令 java -XX:+PrintCommandLineFlags -version查看,这个命令表示运行version程序时,打印出默认的flag。

但是,具体的程序运行的是不是这个gc收集器还不一定,得查看具体的程序jvm使用的是哪个。

jmap -heap [pid] 可以打印出堆的信息,包括使用的GC

或者可以使用jinfo命令来查看,可以使用jinfo -flags [PID] 查看该程序所有默认的和指定的参数,查看是否指定了具体的GC

或者使用jinfo -flag [FLAG] [PID] 来查看是否使用了某种GC,如jinfo -flag UseG1GC 21014 查看程序是否使用了G1GC。

查看公司当前某几个服务的环境,默认是使用UseParallelGC,但是实际上是用的是UseG1GC。

SerialGC

分代算法(复制+标记整理),单线程串行

ParallelGC收集器

主要算法类似于SerialGC,不过他是多线程并行进行gc。所以吞吐量会更高。使用 -XX:+UseParallelGC. 开启,默认情况下,minor 和 major gc都是多线程并行的。

G1收集器(Garbage-First Garbage Collector)

G1是服务器类型的垃圾收集器,适合多处理器大内存的机器运行。他的目标是兼顾高吞吐量和响应时间,满足用户定义的暂停时间目标。在对整个堆进行例如标记操作的过程中,应用的线程也是同时在执行的。

G1中将堆划分为一个个大小相同的内存区域,称为region。每个region都是一片连续的虚拟内存。在G1执行一个并发的全局标记之后,就确定了堆中对象的生存情况,也知道了哪些region里面大部分都是要回收的对象,g1会首先回收这些region,而这些通常会产生大量的空闲内存。这就是为什么g1叫做 Garbage-First,意思就是优先收集垃圾对象。g1使用暂停预测模型来满足用户定义的stw目标时间,他根据指定的stw时间目标来选择要收集的region数量来。

g1会将存活的对象从一个或多个region复制到另外一个region上,并在这个过程中压缩和释放内存。这个操作是在多处理器下并行执行的,以便减少暂停时间,提高吞吐量。因为,每一次的gc,g1都在不断地减少碎片。对比cms,cms只是收集垃圾不进行整理,而Parallel GC是对整个堆进行压缩,这会导致很长暂停时间

g1可以满足用户设定的暂停时间目标,但是不是绝对的,而是高概率满足,他是根据前面的数据,以及一个相当精确的region收集模型来确定暂停时间目标内可以收集哪些region和多少region。

g1在逻辑上也是分代的,虽然说他不像传统的分代堆那样有明确哪一片区域为young和old,而是定义一组空的region为逻辑上的young generation。如下图的浅蓝色区域。对象的分配都会在这些年轻的region上完成,一旦年轻代满了,就会对这些年轻代region进行垃圾回收。在某些场景下,可以对年轻代之外的region进行回收,如老年代,下图中深蓝色区域即为老年代。这通常被称为混合收集。在图中,红色的区域为正在被收集的region。在垃圾收集的同时进行压缩,将存活的对象复制到选定的,初始化过的空region上。根据对象的存活年龄,老对象会晋升到老年代region或者幸存region,如图中标有”S”的region即为幸存region。而下图中的H就是大对象。当一个对象的大小超过region大小的一半时,即被定义为大对象。

image

参考