Java GC-常见垃圾回收器

目录

    • 前言
    • 一、垃圾回收器分类
    • 二、垃圾回收器介绍
      • [1、Serial 收集器](#1、Serial 收集器)
      • [2、ParNew 收集器](#2、ParNew 收集器)
      • [3、Parallel Scavenge 收集器](#3、Parallel Scavenge 收集器)
      • [4、Serial Old 收集器](#4、Serial Old 收集器)
      • [5、Parallel Old 收集器](#5、Parallel Old 收集器)
      • [6、CMS 收集器(多线程标记清除算法)](#6、CMS 收集器(多线程标记清除算法))
      • [7、G1 收集器](#7、G1 收集器)
    • 三、项目中垃圾收集器选型

前言

Java的垃圾回收器其作用是,用于回收程序在运行时所产生的垃圾对象(无引用的对象),因为Java程序一般情况下不用自己释放内存会交由垃圾回收器处理,针对不同业务可以选择不同的垃圾回收器,本文会对Java常见的几种垃圾回收器做介绍

一、垃圾回收器分类

  • Java的垃圾回收器大致可以按照三个维度划分,每种垃圾回收器都有自己的特点。
    按照所负责回收区域划分:新生代回收器,老年代回收器
    按照GC执行的资源成本划分:单线程回收,多线程回收
    按照与用户线程关系划分:需暂停用户线程,与用户线程并发
收集器 回收区域 收集算法 回收器特征
Serial 新生代 标记-复制 单线程
ParNew 新生代 标记-复制 单线程
Parallel Scavenge 新生代 标记-复制 单线程
Serial Old 老年代 标记-整理 单线程
Parallel Old 老年代 标记-整理 多线程
CMS 老年代 标记-清除 并发多线程
G1 (JDK1.7推出, JDK1.9默认垃圾回收器) 跨代回收 整体:标记-整理 局部Region:标记-复制 并发多线程

二、垃圾回收器介绍

1、Serial 收集器

这是一个单线程的垃圾回收器,即执行垃圾回收操作时只有一个GC线程工作,最重要的是在他执行GC过程中,必须全程暂停用户线程,也就是经常说的STW(Stop the world)问题,这是jvm最早期的收集器,虽然STW问题是确实是一个诟病,但是这种方案实现起来较简单,占用资源也是最小的,对应新生代内存比较小的应用中,相对比较适用,比如运行在客户端模式下的Java程序。

2、ParNew 收集器

和Serial 收集器不同的是,这是一款多线程收集器,即有多条GC线程同时进行垃圾回收工作,剩余其他的特点和Serial 收集器基本无异,比如控制参数,回收算法,STW问题等,作为一款新生代收集器,常用来和CMS搭配使用,ParNew是激活CMS后默认的新生代收集器,由于多线程执行的原因,在单核CPU下,由于线程上下文的切换,该收集器的效果甚至不如Serial收集器,在多核CPU中才推荐使用。

3、Parallel Scavenge 收集器

Parallel Scavenge与ParNew实现上差别不大,不同的是,该收集器的侧重点是虚拟机的吞吐量,吞吐量很好理解,即用户代码的执行时间占整个系统运行时间的比重,其中就包括GC时间。如果虚拟机运行期间GC消耗的时间,占用的资源相对较高,那吞吐量自然也就会下降。而Parallel Scavenge关注的就是吞吐量的可控,尽可能的减少GC时间,让用户线程执行更长的时间,一些交互性比较弱的应用,比如科学计算、批处理任务、订单流转等。

为了实现这个目标,Parallel Scavenge提供了一些参数:
-XX:MaxGCPauseMills 即"最大GC停顿毫秒值" , 该值设置的大小是以新生代回收空间为代价的,设置的值越小,意味着所能回收的空间也会越小,相应的GC频率自然会提高,这个时候未必会提高吞吐量,具体的设置多少,需要根据应用的实际场景来决定
-XX:GCTimeRatio 该参数的范围是大于0小于100的整数,意义是用户程序的运行时间和垃圾回收时间的比例,比如设置为99,那最大允许的垃圾回收时间为1%。
-XX:+UseAdaptiveSizePolicy 直译过来就是"自适应大小策略",当这个参数被激活后,就不需要指定新生代、Eden和Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等参数,虚拟机会根据当前的运行状态,收集性能监控信息,动态调整这些参数以提供最合适的停顿时间和最大吞吐量。

4、Serial Old 收集器

Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。

在 Server 模式下,主要有两个用途:1. 在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。2. 作为年老代中使用 CMS 收集器的后备垃圾收集方案

  • 新生代 Serial 与年老代 Serial Old 搭配垃圾收集过程:
  • 新生代 Parallel Scavenge/ParNew 与年老代 Serial Old 搭配垃圾收集过程:

5、Parallel Old 收集器

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge和年老代 Parallel Old 收集器的搭配策略。

6、CMS 收集器(多线程标记清除算法)

Concurrent mark sweep(CMS) 收集器是一种周期性老年代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。

  • CMS垃圾回收器垃圾回收主要分为4个阶段:
    • 1、初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
    • 2、并发标记:该阶段GC线程和应用线程并发执行,遍历初始标记阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。
    • 3、重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程,但是因为在前面两个阶段已经将大部分对象已经标记,这一步虽然需要暂停用户线程,但是这个暂停时间也不会很长。
    • 4、并发清除:这个阶段就是真正的清除垃圾对象的阶段,和用户线程一起工作,不需要暂停工作线程,会将前几步标记的垃圾对象清除。

7、G1 收集器

(Garbage first)G1 垃圾收集器采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域(1MB~32MB,且必须是2的幂),每个Region根据需要都可以称为新生代的Eden区、Suivivor区,或者老年代区,收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象,还是已经存活了一段时间的对象都能获得良好的收集效果。

同时Region还有一类特殊的humongous区域,专门用来存储大对象,只要超过了一个Region容量的一半就会被认为是大对象,如果对象长度超过了Region的大小,则会使用多个连续的humongous来存放,G1的大多数行为会把该区域当做老年代来看待。

G1依然保留了新生代和老年代的概念,逻辑上分代,物理上分区,内存中新生代和老年代不是固定的,他们是一系列的"动态集合",通过这个动态集合建立一个可预测的停顿时间模型,换句话说,G1的回收是建立在这个停顿时间模型上的。每次回收都是以Region为单位,让G1去跟踪每一个Region中垃圾的"价值大小",比如回收空间大小和回收所需的时间,然后通过这个价值大小维护一个优先级的列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值最大的那些Region,这种划分Region空间以及具有优先级的区域回收方式,使得G1在有限时间内尽可能获得更高的收集效率。

  • G1垃圾回收执行过程可分为以下四步:
    • 初始标记:标记gc roots直接关联的对象,修改TAMS指针的值,目的是下阶段并发回收时,能够继续正常的分配对象。这个阶段需要停顿用户线程,但是相对比较短暂。
    • 并发标记:从gc roots开始对对象图进行可达性分析,找到有回收的对象,这个阶段与用户线程并发。
    • 最终标记:对并发标记时引用发生变化的对象重新标记,通过原始快照的方式,毫无疑问,这阶段需要短暂暂停用户线程
    • 筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,结合用户指定的停顿时间制定回收计划,可以自由选择任意多个region构成回收集,然后把决定回收的那一部分region中存活的对象复制到空的region中,再清理掉整个旧region空间,由于要移动对象,所以这个阶段也必须暂停用户线程,有多个收集器线程并行完成 。

      通过以上过程可以看出,除了并发标记阶段,其他阶段都需要暂停用户线程,所以G1主要目标是在延迟可控的情况下获得尽可能高的吞吐量,希望在延迟和吞吐量之间达到一个平衡。

三、项目中垃圾收集器选型

  • 如何合理选择收集器(项目配置堆内存有关系):
    • 1、入门级别网站 0.5-1GB,可以使用单线程新生代:Serial、老年代:Serial Old
    • 2、有一定的访问量 1-4G,新生代:ParNew | Parallel Scavenge、老年代:Parallel Old
    • 3、并发适中 4G左右,新生代:ParNew | Parallel Scavenge、老年代:CMS(响应式优先)
    • 4、高并发项目8G以上,G1收集器(注重吞吐和响应时间)
相关推荐
一个小坑货6 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet2710 分钟前
【Rust练习】22.HashMap
开发语言·后端·rust
古月居GYH11 分钟前
在C++上实现反射用法
java·开发语言·c++
在下不上天36 分钟前
Flume日志采集系统的部署,实现flume负载均衡,flume故障恢复
大数据·开发语言·python
陌小呆^O^1 小时前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp
请你打开电视看看1 小时前
Jvm知识点
jvm
儿时可乖了1 小时前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol1 小时前
java基础概念37:正则表达式2-爬虫
java