JVM垃圾收集器

问题

  • 带着问题去学习,效率更高!

  • 垃圾收集器和垃圾回收算法的关系?分别请你谈谈?

  • 怎么查看服务器的垃圾收集器是哪个?如何配置垃圾收集器?谈谈你对垃圾收集器的理解?

  • 你知道G1垃圾收集器吗?

垃圾收集算法

  • 垃圾收集器和垃圾回收算法的关系?
    垃圾算法(引用计数,复制算法,标记清除,标记整理)都是内存回收的方法论,垃圾收集器是这种算法的落地实现。

  • 请移步JVM中的GC收集算法有哪些

先了解相关概念

垃圾收集器主要发生的区域

主要发生在方法区和堆中,其中方法区在jdk1.8中的实现是元空间(Metaspace)

次收集器

发生在新生代中的GC,被称为Young GC(Scavenge GC,Minor GC),
新生代中的对象大多数生命周期比较短,所以这里的GC发生很频繁。

全收集器

Full GC,指发生在老年代的 GC,出现了 Full GC 一般会伴随着至少一次的 Young GC(老
年代的对象大部分是Young GC 过程中从新生代进入老年代),比如:分配担保失败。Full
GC 的速度一般会比 Young GC 慢 10 倍以上。

什么时候触发GC

  • 当 Eden 空间不足以为对象分配内存时,会触发 Young GC。
  • 当老年代内存不足,或者显式调用 System.gc() 会触发Full GC(也会伴随着执行至少一次的Young GC)。

STW(Stop-the-world)和并发

GC在运行的时候,暂停整个应用就叫STW。
并发这里只的是GC线程和用户线程一起运行,GC可能占用用户的线程。

串行和并行

串行:垃圾回收的时候只有一个GC线程工作。
并行:垃圾回收的时候有多个GC线程一起工作。
在单核的情况下,并行可能更慢。

四种主要的垃圾收集器

  • 串行收集器
    适用于单线程系统,适用一个线程进行垃圾回收,会暂停所有的用户线程,不适合服务器环境。
  • 并行收集器
    多个垃圾收集器并行工作,此时用户线程是暂停的,适用于计算/大数据处理,
    适用于弱交互的场景,强调吞吐量。
  • CMS收集器(Concurrent Mark Sweep的简写 并发标记,清除 )
    用户线程和垃圾收集线程同时执行(不一定并行,可能交替执行)不需要暂停用户线程,互联网公司多数使用它,
    适用于强交互,对响应时间有要求的场景。
  • G1收集器
    将堆内存分割成不同的区域,并行的进行垃圾收集。

后台日志中的部分参数解释

  • DefNew 相当于 Default New Generation的缩写 默认的年轻代使用的垃圾回收器是什么
  • Tenured 相当于 old
  • ParNew 相当于 Parallel New Generation 年轻代使用的是并行回收
  • PSYoungGen 其中ps相当于Parallel Scavenge
  • ParOldGen 相当于Parallel Old Generation 老年底并行垃圾收集器

垃圾收集的组合

年轻代收集器(复制算法)

年轻代串行收集器(serial [ˈsɪəriəl] 或serial copying)

特征

单线程收集
收集时必须Stop The Word(停止所有的用户线程)
jvm内存不大时停顿时间短(说明堆内存大小影响GC的效率)

对应JVM参数: -XX:UseSerialGC

配置jvm的时候只需要配置年轻代的收集器即可,jvm会自动选择匹配的老年代收集器。
开启后会使用:年轻代使用Serial +老年代使用的是Serial Old 的收集器组合,说明都是使用的串行。
使用的算法为:年轻代使用复制算法,老年代使用标记-整理算法;

图例:

年轻代并行收集器(ParNew)

特征

多线程收集(是Serial的多线程版本)
单cpu的效率不及串行收集器Serial,多cpu肯定快于Serial
收集的过程中还是会暂停其他的用户线程。
可以控制ParNew的运行线程数,默认情况下线程数量和CPU核数的数目相同。

常用对应的JVM参数:-XX:UseParNewGC

启用ParNew收集器只会影响年轻代的手机,不影响老年代。
开启后会使用:年轻代使用ParNew收集器, 老年代使用Serial Old 收集器,
注意:jdk8已经不推荐这么组合了!因为Serial Old要被弃用了。
使用的算法为:年轻代使用复制算法,老年代使用标记-整理算法;
-XX:UseParallelGCThreads这个参数可以设置并行收集的线程数。(CPU>8 设置N=5/8 CPU<8 设置Cpu实际个数)

图例

年轻代并发收集器(Parallel Scavenge)

读音:[ˈpærəlel]

和ParNew的区别

  • ParNew在新生代是并行,对应的老年代的却是串行,但是Parallel Scavenge 是并行的。
  • Parallel Scanvenge更强调系统的吞吐

特征

多线程并发收集器,吞吐量优先。
这个收集器通常用于程序运算任务,不需要太多的交互任务。
自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别
(虚拟机会根据当前系统的运行情况,动态调整参数设置一个最合适的停顿时间或者最大吞吐量 -XX:MaxGCPauseMillis 这个参数可以设置)
一句话概括:新生代和老年代都使用并行收集。
前面的Serial以及ParNew年轻代的收集器对应的老年代的收集器都是Serial Old都是串行的,这个收集器对应的老年代收集器是并行的(Parallel Old)

  • 什么是系统吞吐量

系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
系统吞吐量越接近1吞吐量越高。假设运行用户代码的时间为1000ms,垃圾收集时间为100ms,那这个吞吐量结果为:0.90

常用对应的JVM参数:-XX:UseParallelGC 或 -XXUseParallelOldGC 这两个可以相互激活

开启后会使用:年轻代使用Parallel Scavenge收集器, 老年代使用Parallel Old收集器,
使用的算法为:年轻代使用复制算法,老年代使用标记-整理算法;
-XX:UseParallelGCThreads这个参数可以设置并行收集的线程数。

图例

老年代收集器

老年代串行收集器(Serial Old )

  • 特征

单线程收集器
使用的是标记整理算法(可以处理空间碎片问题)
它和年轻代的Serial以及ParNew配合使用,它被作为老年代收集器CMS的备选收集方案。
注意:jdk8已经放弃使用了!

老年代的并行收集器(Parallel Old)

  • Parallel Old 是 Parallel Scavenge 收集器的老年代版本,;
  • 使用多线程和“标记-整理”算法, 注重吞吐量;

老年代的并发收集器【CMS 收集器 (Concurrent Mark Sweep 并发标记,清除 )】

特征

CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器)
适用于互联网站或者B/S系统的服务器上,因为GC的停顿时间短,响应速度就快了!
并发收集所以停顿时间短 (注意:这里要区别上面的并行和串行,并发收集指的是GC线程和用户线程是同时运行的。)
采用标记-清除算法(会产生内存碎片问题,执行几次cms会进行一次整理)

基于”标记-清除”算法实现, 整个 GC 过程分为以下 4 个步骤:

  • 初始标记(CMS initial mark)
    只是标记一下GC Roots能直接关联的对象,并没有把真个引用链标记完整,速度很快,但是需要暂停用户线程。
  • 并发标记(CMS concurrent mark: GC Roots Tracing 过程)
    在这个过程中根据初始标记的对象会把整个对象引用链标记处理,不需要暂停用户线程,和用户线程一起运行
  • 重新标记(CMS remark)
    为了修正在并发标记期间,产生变动对象的那一部分记录,仍然需要暂停用户线程。
  • 并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩)
    清除GC Roots不可达对象,不需要暂停用户线程。
  • 由于耗时最长的并发标记和并发清除过程,都是和用户线程并发执行的,所以总体上来说CMS收集器和用户线程是并发执行的。

缺点:

  • 并发执行对CPU资源压力大
    由于并发进行,CMS在收集与用户线程会同时增加对堆内存的占用,也就是说CMS必须要在老年代对堆内存用尽之前完成一次垃圾回收,否则CMS回收失败时,
    将触发担保机制,串行老年代收集器(Serial Old)将会以STW的方式进行一次GC,从而造成较大停顿。
  • 无法处理浮动垃圾(在并发清除阶段用户线程产生的新的垃圾)
  • CMS是一款“标记--清除”算法实现的收集器,容易出现大量空间碎片,最后不得不通过担保机制对堆内存进行整理压缩。
    我们可以通过参数配置经过多少次CMS才进行一次的整理压缩。
    参数:-XX:CMSFullGCsBeForeCompaction(默认为0,每次都进行内存整理压缩)

JVM对应常用的参数:-XX:UseConcMarkSweepGC

开启该参数会自动将-XX:UseParNewGC打开;
开启后会使用:年轻代使用ParNew 老年代使用CMS+Serial Old(担保)
使用的算法为:年轻代使用复制算法,老年代使用标记-清除,标记-整理

G1收集器(Garbage-First )

特征

G1能充分利用多CPU,多核硬件环境,尽量缩短STW
G1整体采用标记-整理算法,局部通过复制算法,不会产生内存碎片
G1把整个内存区混合在一起了,把内存划分成多个独立的Region区块。
保留分代收集,但是,内存分区物理内存上区分年轻代和老年代,每一个Region都有可能是年轻代,老年代,而且角色随着G1的运行可以变换。

内存的物理划分

  • 取消了新生代,老年代的【物理空间】划分,G1采用将堆划分为若干个大小相同的区域(Region) , 最大的好处是:避免GC的全内存扫描。
  • G1虽然取消了堆内存物理上老年代和新生代的划分,但是他在Region区域中仍然采用的是分代收集,如上图:E 表示Eden区,S表示Survivor区,O表示Old区,H表示存放大对象的,但是:每个Region的角色不是固定的,是可以变换的。也就是说年轻代和老年代可以互换。
  • 关于Region的大小:JVM启动时会自动设置大小,我们也可以通过参数-XX:G1HeapRegionSize=n 指定(1~32MB 必须2的幂),默认分成了2048个分区。

G1垃圾回收模式

Young GC (针对年轻代)

  • G1的Young GC会暂停所有用户线程
  • G1的Young GC主要针对Eden区进行收集,Eden Region耗尽后会被触发Young GC:
  • G1的Young GC的大致过程(对比着上图看):
  • 对Eden区的存活数据会移动到Survivor区,假如出现Survivor Region区域空间不够,Eden区的部分数据会被晋升到Old区
  • 将Survivor区存活的数据移动到新的Survivor区中,部分数据晋升到Old区中。
  • 最后Eden区进行清除,压缩,GC结束后,用户线程继续执行。

Mixed GC(针对老年代)

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器;
除了回收整个young region,还会回收一部分(非全部)的old region;
可以设置Mixed GC什么时候触发: -XX:InitiatingHeapOccupancyPercent 当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixed gc.

  • 运行过程
    和CMS差不多。

Full GC(针对整个堆内存)

如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc

G1的常用配置参数:

-XX:+UseG1GC 开启G1垃圾收集
-XX:G1HeapRegionSize=n 设置每一个Region的大小(1~32MB 必须2的幂),默认分成了2048个分区
-XX:MaxGCPauseMillis=n 最大的GC停顿时间,jvm不保证能够达到。
-XX:InitiatingHeapOccupancyPercent 老年代占用堆内存的百分比就触发GC,默认45%

G1与CMS的区别

  • 和CMS一样并发执行。
  • G1不会产生内存碎片。
  • G1的GC停顿时间可控,用户可以指定,jvm尽可能达到但不保证。
  • G1采用Region区域化的垃圾收集器

收集器配置参数总结

参数 新生代垃圾收集器 新生代算法 老年代垃圾收集器 老生代算法
-XX:+UseSerialGC Serial GC 复制算法 Serial Old 标记整理
-XX:+UseParNewGC ParNew 复制算法 Serial Old 标记整理
-XX:+UseParallelGC Parallel Scavenge 复制算法 Parallel Old 标记整理
-XX:+USeParallelOldGC Parallel Scavenge 复制算法 Parallel Old 标记整理
-XX:+UseConcMarkSweepGC ParNew 复制算法 CMS+Serial Old 标记清除+整理
-XX:+UseG1GC G1整体采用标记整理 局部是通过复制算法

如何查看JVM垃圾收集器?

查看默认的垃圾收集器是哪个?

使用命令:java -XX:+PrintCommandLineFlags -version

默认的垃圾收集器有哪些?

我们在配置垃圾收集器的时候只需要指明新生代或者老年代的收集器即可,jvm会自动选择老年代或者新生代的收集器
下面是设置年轻代的GC收集器

  • -XX:+UseSerialGC (自动组合:Serial+Serial Old)
  • -XX:+UseParNewGc (自动组合:ParNew+ Serial Old)
  • -XX:+UseParallelGc (自动组合:Parallel Scavenge +Parallel Old)

下面是设置老年代的GC收集器

  • -XX:+UseConcMarkSweepGC (自动组合:ParNew + CMS+ Serial Old)
  • -XX:+UseParallelOldGc (自动组合:ParallelNew +Parallel Old)
  • -XX:+UseG1GC
  • 从JVM的源代码可以看出,我们不能随意组合,否则JVM启动不了,会报异常!
bool Arguments::check_gc_consistency() {  
  bool status = true;  
  uint i = 0;  
  if (UseSerialGC)                       i++;  
  if (UseConcMarkSweepGC || UseParNewGC) i++;  
  if (UseParallelGC || UseParallelOldGC) i++;  
  if (UseG1GC)                           i++;  
  if (i > 1) {  
    jio_fprintf(defaultStream::error_stream(),  
                "Conflicting collector combinations in option list; "  
                "please refer to the release notes for the combinations "  
                "allowed\n");  
    status = false;  
  }  

  return status;

查看正在运行的JVM垃圾收集器?

  • 使用jps查出进程,使用jinfo打印运行的jvm参数 jinfo -flags PID

  • 可以使用Jconsole,jvisualvm可视化工具进行查看

  • 在项目启动的时候添加JVM参数:-XX:+PrintCommandLineFlags 也可以获得。

生产上怎么选择收集器?

选择收集器应该考虑到服务器硬件的配置,以及应用程序的特点;

  • 单CPU或者内存小的服务器,选择串行收集器
    -XX:+UseSerial
  • 多CPU,需要打的吞吐量的,选择并行收集器,如:后台计算较多的应用,
    -XX:+UseParallelGC 或者 -XX:+UseParallelOldGC
  • 多cpu,追求停顿时间短的,选择并发收集器,如:需要快速响应的互联网应用
    -XX:+UseConcMarkSweepGC

jvm运行时数据区


这个图和上面的jvm总体结构图没有冲突,这里只是强调一个线程在运行方法的时候涉及到的内存。

逃逸分析

  • 决定这个变量或者对象实例是在栈中分配还是堆中分配,逃逸指的是这个变量或者对象实例的作用域是否超过当前方法(逃出了方法)。
    如果一个对象实例仅仅在当前方法中使用,并没有通过【返回值返回出去】或者 【通过调用别的方法时作为参数传递过去】
    那么这个对象就是没有发生逃逸,可以将这个对象分配在java栈空间中,栈空间是随着方法的执行结束而清理的,不需要GC,所以可以提高效率。

  • jvm中逃逸分析是可以设置开关的,默认是开启的。

其他概念

对象的创建过程的内存分配

https://www.cnblogs.com/wangsen/p/11243049.html

对象的引用(强,软,弱,虚)

https://www.cnblogs.com/wangsen/p/11206956.html
https://www.cnblogs.com/rgever/p/8902210.html

posted @ 2019-07-20 17:27  王森  阅读(608)  评论(0编辑  收藏  举报