各个版本的JDK对应的垃圾回收器

版本YoungOld
JDK6PSScavenge(Parallel Scavenge)PSMarkSweep(Parallel Scavenge)
JDK7PSScavenge(Parallel Scavenge)PSParallelCompact(Parallel Old)
JDK8PSScavenge(Parallel Scavenge)PSParallelCompact(Parallel Old)
JDK11G1G1

1、垃圾回收机制?

答:在学习Java GC 之前,我们需要记住一个单词:stop-the-world(STW) 。它会在任何一种GC 算法中发生stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行。当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成。GC 优化很多时候就是减少 stop-the-world 的发生或者说是减少 stop-the-world 的时间

2、GC回收哪个区域的垃圾?

答:JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出作用域后会被JVM自动释放掉,所以其不在JVM GC的管理范围内。

3、GC怎么判断对象可以被回收?

答:某个对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。

  • 对象没有引用;
  • 作用域发生未捕获异常;
  • 程序在作用域正常执行完毕;
  • 程序执行了System.exit()
  • 程序发生意外终止(被杀线程等)。

4、Java 中都有哪些引用类型?

  • 强引用: 发生 GC 的时候不会被回收。
  • 软引用: 有用但不是必须的对象,在发生内存溢出之前会被回收。
  • 弱引用: 有用但不是必须的对象,在下一次GC时会被回收。
  • 虚引用(幽灵引用/幻影引用): 无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 GC 时返回一个通知。

5、怎么判断对象是否可以被回收?

  • 引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题

  • 可达性分析:GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Root 没有任何引用链相连时,则证明此对象是可以被回收的。

    GC ROOTS包含哪些?

    • 在虚拟机栈中引用的对象;

    • 方法区中类静态属性引用的对象;

    • 方法区中常量引用的对象;

    • 本地方法栈中JNI引用的对象;

    • Java虚拟机内部的引用,如基本数据类型对应的class对象等;

    • 所有被同步锁(synchronized 关键字)持有的对象;

    • 反映Java虚拟机内部情况的JMXBeanJVMTI中注册的回调、本地代码缓存等。

6、GC算法

答: 根搜索算法(所有GC算法都引用根搜索算法这种概念)是从离散数学中的图论引入的,程序把所有引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点。当所有的引用节点寻找完毕后,剩余的节点则被认为是没有被引用到的节点,即无用的节点

在这里插入图片描述

  • 标记-清除算法: 标记无用对象,然后进行清除回收。缺点:由于标记-清除算法直接回收不存活的对象,并没有对还存活的对象进行整理效率不高,无法清除垃圾碎片

    在这里插入图片描述

  • 标记-整理算法: 标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。缺点:成本更高。
    在这里插入图片描述

  • 复制算法:按照容量划分两个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半
    在这里插入图片描述

  • 分代算法: 根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法

  • 三色算法(CMSG1):

    • 黑色: 该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
    • 灰色: 该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
    • 白色: 该对象没有被标记过。(对象垃圾)

6、Minor GCMajor GCFull GC 介绍

6.1、Minor GC 清理年轻代

答: Minor GC指新生代GC,即发生在新生代(包括Eden区和Survivor区)的垃圾回收操作。当新生代无法为新生对象分配内存空间的时候,会触发Minor GC。因为新生代中大多数对象的生命周期都很短,所以发生Minor GC的频率很高,虽然它会触发 stop-the-world,但是它的回收速度很快

6.2、Major GC 清理老年代

答:Major GC清理Tenured区,用于回收老年代,出现Major GC通常会出现至少一次****Minor GC

6.3、Full GC 清理整个堆空间—包括年轻代、老年代和元空间

答:Full GC是针对整个新生代、老生代、元空间(metaspace,Java8以上版本取代perm gen)的全局范围的 GCFull GC不等于Major GC,也不等于Minor GC + Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

Major GC 通常是跟 full GC 是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人major GC的时候一定要问清楚他想要指的是上面的full GC还是老年代。

7、JVM GC 什么时候执行?

答:

  • Minor GC 系统自动触发的机制只有一个,就是Eden没有足够的空间分配给新创建的对象
  • Full GC
    • 老年代空间不足,这个很简单,就是字面上的不足,例如:大对象不停的直接进入老年代,最终造成空间不足;
    • 方法区空间不足
    • Minor GC 引发 Full GC 这个才是本文想重点介绍的。

8、为什么 Minor GC 会引发 Full GC 呢?引发条件是什么?

答: 年轻代的对象在经历Minor GC 过后,部分对象存活对象或全部存活对象会进入老年代。新生代与老年代的比例的值为1:2 (该值可以通过参数 –XX:NewRatio 来指定)。

引发条件

  • 老年代剩余连续内存空间 > 新生代对象总空间>历次晋升到老年代的对象的平均大小,万事大吉,Minor GC 直接运行;
  • 新生代对象总空间>老年代剩余连续内存空间>历次晋升到老年代的对象的平均大小,这下又要分情况了,主要是看是否设置了 HandlePromotionFailure 参数,JDK1.6之后该参数废弃了,但是机制仍在。执行Minor GC
    • 第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活对象进入Survivor区域即可;
    • 第二种可能,Minor GC过后,剩余的存活对象的大小,是大于Survivor区域的大小,但是是小于老年代可用内存大小 的,此时就直接进入老年代即可;
    • 第三种可能,很不幸,Minor GC 过后,剩余的存活对象的大小,大于了 Survivor 区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触 发一次“Full GC”。 如果 Full GC 仍空间不够就会 OOM
  • 新生代对象总空间> 历次晋升到老年代的对象的平均大小>老年代剩余连续内存空间: 则不会触发Minor GC而是转为触发full GC(因为HotSpot VMGC里,除了CMSconcurrent collection之外,其它能收集old genGC都会同时收集整个GC堆,包括新生代,所以不需要事先触发一次单独的Minor GC

9、按代的垃圾回收机制

答:默认的新生代(Young generation):老年代(Old generation )所占空间比例为 1 : 2 。

  • 新生代(Young generation:绝大多数最新被创建的对象都会被分配到这里,由于大部分在创建后很快变得不可达,很多对象被创建在新生代,然后“消失”。对象从这个区域“消失”的过程我们称之为:Minor GC
  • 老年代(Old generation:对象没有变得不可达,并且从新生代周期中存活了下来,会被拷贝到这里。其区域分配的空间要比新生代多。也正由于其相对大的空间,发生在老年代的GC次数要比新生代少得多。对象从老年代中消失的过程,称之为:Major GC 或者 Full GC
  • 持久代(Permanent generation)也称之为 方法区(Method area):用于保存类常量以及字符串常量。注意,这个区域不是用于存储那些从老年代存活下来的对象,这个区域也可能发生GC。发生在这个区域的GC事件也被算为 Major GC 。只不过在这个区域发生GC的条件非常严苛,必须符合以下三种条件才会被回收:
    • 所有实例被回收
    • 加载该类的ClassLoader 被回收
    • Class对象无法通过任何途径访问(包括反射)

为什么老年代用标记整理算法,新生代用复制算法

答: 新生代的对象往往都是“朝生暮死”,因此新生代的对象在标记之后存活的对象较少,可以通过复制来提高算法的效率。老年代一般不会发生GC,加之老年代GC的对象也较少,因此采用标记整理法进行清理,可以有效的提高效率。

10、如果老年代的对象需要引用新生代的对象,会发生什么呢?

答: 为了解决这个问题,老年代中存在一个 card table,它是一个512byte大小的块。所有老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只需要查询 card table 来决定是否可以被回收,而不用查询整个老年代。这个card table 由一个 write barrier 来管理。write barrierGC带来了很大的性能提升,虽然由此可能带来一些开销,但完全是值得的。

11、新生代空间的构成?

答: 为了更好的理解GC,我们来学习新生代的构成,它用来保存那些第一次被创建的对象,它被分成三个空间:

· 一个伊甸园空间(Eden

· 两个幸存者空间(Fron SurvivorTo Survivor

默认新生代空间的分配:****Eden : Fron : To = 8 : 1 : 1

每个空间的执行顺序如下:

1、绝大多数刚刚被创建的对象会存放在伊甸园空间(Eden)。

2、在伊甸园空间执行第一次 GC Minor GC 之后,存活的对象被移动到其中一个幸存者空间(Survivor)。

3、此后,每次伊甸园空间执行GC后,存活的对象会被堆积在同一个幸存者空间

4、当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。然后会清空已经饱和的哪个幸存者空间。

5、在以上步骤中重复N次( N = MaxTenuringThreshold (年龄阀值设定,默认15)) 依然存活的对象,就会被移动到老年代。

从上面的步骤可以发现,两个幸存者空间,必须有一个是保持空的。如果两个两个幸存者空间都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误。

我们需要重点记住的是,对象在刚刚被创建之后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。也有例外出现,对于一些比较大的对象(需要分配一块比较大的连续内存空间)则直接进入到老年代。一般在 Survivor 空间不足的情况下发生。

12、老年代空间的构成与逻辑

答:老年代空间的构成其实很简单,它不像新生代空间那样划分为几个区域,它只有一个区域,里面存储的对象并不像新生代空间绝大部分都是朝闻道,夕死矣。这里的对象几乎都是从 Survivor 空间中熬过来的,它们绝不会轻易的狗带。因此,Full GC(Major GC)发生的次数不会有Minor GC 那么频繁,并且做一次 Major GC 的时间比 Minor GC 要更长(约10倍)

13、说一下 JVM 有哪些垃圾回收器?

答:如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括SerialPraNewParallel Scavenge回收老年代的收集器包括Serial OldParallel OldCMS,还有用于回收整个Java堆G1收集器。不同收集器之间的连线表示它们可以搭配使用。

在这里插入图片描述

  • Serial (复制算法):最早的单线程串行垃圾回收器。
  • ParNew (复制算法):是 Serial多线程版本。
  • Parallel Scavenge (复制算法):ParallelParNew收集器类似是多线程的,但 Parallel Scavenge 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量
  • Serial Old (标记-整理法):Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。
  • Parallel Old (标记整理法):Parallel OldParallel 老生代版本,Parallel 使用的是复制的内存回收算法Parallel Old 使用的是标记-整理的内存回收算法。
  • CMS (标记-整理法):一种以牺牲吞吐量为代价来获得最短回收停顿时间为目标的收集器,非常适用 B/S 系统。
  • G1 (标记-整理法 + 复制算法):一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK9 以后的默认 GC 选项。

12、详细介绍一下 CMS 垃圾回收器?

答:

  • CMS是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX: + UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
  • CMS 使用的是标记-清理的算法实现的,所以在 GC 的时候会产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低
  • 分为四个主要步骤初始标记、并发标记、重新标记、并发清除,详细步骤如下:
    • 初始标记(STW initial mark):在这个阶段,需要虚拟机停顿正在执行的应用线程,官方的叫法STWStop To World)。这个过程从根对象扫描直接关联的对象,并作标记。这个过程会很快的完成。
    • 并发标记(Concurrent marking): 这个阶段紧随初始标记阶段,在“初始标记”的基础上继续向下追溯标记。注意这里是并发标记,表示用户线程可以和 GC 线程一起并发执行,这个阶段不会暂停用户的线程哦。
    • 并发预清理( Concurrent precleaning ):这个阶段任然是并发的,JVM查找正在执行“并发标记”阶段时候进入老年代的对象(可能这时会有对象从新生代晋升到老年代,或被分配到老年代)。通过重新扫描,减少在一个阶段“重新标记”的工作,因为下一阶段会STW
    • 重新标记(STW remark):这个阶段会再次暂停正在执行的应用线程,重新重根对象开始查找并标记并发阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致),并处理对象关联。这一次耗时会比“初始标记”更长,并且这个阶段可以并行标记。
    • 并发清理(Concurrent sweeping):这个阶段是并发的,应用线程和GC清除线程可以一起并发执行。
    • 并发重置(Concurrent reset):这个阶段仍然是并发的,重置CMS收集器的数据结构,等待下一次垃圾回收。

缺点:

  • 内存碎片:由于使用了 标记-清理算法,导致内存空间中会产生内存碎片。不过CMS收集器做了一些小的优化,就是把未分配的空间汇总成一个列表,当有JVM需要分配内存空间的时候,会搜索这个列表找到符合条件的空间来存储这个对象。但是内存碎片的问题依然存在,如果一个对象需要3块连续的空间来存储,因为内存碎片的原因,寻找不到这样的空间,就会导致****Full GC
  • 需要更多的 CPU 资源: 由于使用了并发处理,很多情况下都是GC线程和应用线程并发执行的,这样就需要占用更多的CPU资源,也是牺牲了一定吞吐量的原因。
  • 需要更大的堆空间:因为CMS标记阶段应用程序的线程还是执行的,那么就会有堆空间继续分配的问题,为了保障CMS在回收堆空间之前还有空间分配给新加入的对象,必须预留一部分空间。CMS默认在老年代空间使用68%时候启动垃圾回收。可以通过-XX:CMSinitiatingOccupancyFraction=n来设置这个阀值。

13、详细介绍一下 G1 垃圾回收器?

答:开创了收集器面向局部收集的设计思路和基于 Region 的内存布局,主要面向服务端,最初设计目标是替换 CMS

G1 之前的收集器,垃圾收集目标要么是整个新生代,要么是整个老年代或整个堆。而 G1 可面向堆任何部分来组成回收集进行回收,衡量标准不再是分代,而是哪块内存中存放的垃圾数量最多,回收受益最大。跟踪各 Region 里垃圾的价值,价值即回收所获空间大小以及回收所需时间的经验值,在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值最大的 Region。这种方式保证了 G1 在有限时间内获取尽可能高的收集效率。

运行过程:

  • 初始标记(会 STW ):标记 GC Roots 能直接关联到的对象,让下一阶段用户线程并发运行时能正确地在可用Region 中分配新对象。需要 STW 但耗时很短,在 Minor GC时同步完成;
  • Root Region Scanning 根区域扫描: 根区域扫描是从Survior区的对象出发,标记被引用到老年代中的对象,并把它们的字段在压入扫描栈(marking stack)中等到后续扫描。与Initial Mark不一样的是,Root Region Scanning不需要STW与应用程序是并发运行。Root Region Scanning必须在YGC开始前完成;
  • 并发标记( Concurrent Marking ): 这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息;
  • 最终标记(RemarkSTW): 标记那些在并发标记阶段发生变化的对象,将被回收;
  • 清除垃圾(Cleanup): 清除空Region(没有存活对象的),加入到free list

G1从整体来看是基于标记-整理 算法实现的回收器,但从局部(两个Region之间)上看又是基于 标记-复制 算法实现的。

SATB也是有副作用的,如果被替换的白对象就是要被收集的垃圾,这次的标记会让它躲过GC,这就是float garbage。因为 SATB 的做法精度比较低,所以造成的float garbage也会比较多。

14、CMS收集器和G1收集器的区别:

  • CMS收集器是老年代的收集器,可以配合新生代的SerialParNew收集器一起使用;G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用。
  • CMS收集器以最小的停顿时间为目标的收集器;G1收集器可预测垃圾回收的停顿时间。
  • CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片;G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

15、频繁产生Full GC是什么原因?

  • 系统并发高、执行耗时过长,或者数据量过大,导致 young gc频繁,且gc后存活对象太多,但是survivor 区存放不下(太小 或 动态年龄判断) 导致对象快速进入老年代,老年代迅速堆满;
  • 程序一次性加载过多对象到内存 (大对象),导致频繁有大对象进入老年代造成full gc
  • 存在内存溢出的情况,老年代驻留了大量释放不掉的对象,只要有一点点对象进入老年代就达到full gc的水位了;
  • 元数据区加载了太多类 ,满了 也会发生 full gc
  • 堆外内存 direct buffer memory 使用不当导致。

16、频繁产生Full GC 怎么排查问题进行调整?

答:

  • 通过JVM参数获取dump文件;
# 1.线上环境如果有流量需要在启动服务脚本中加入如下JVM参数,表示在发生fullgc的时候自动dump -XX:HeapDumpBeforeFullGC
# 2.与第一个JVM参数配套使用,指定dump文件的保存路径,便于排查问题,路径也可以是相对路径
-XX:HeapDumpPath=保存dump文件的文件绝对路径

# 说明:如果加入这两个jvm参数还是没有dump下来文件,可能是你的jvm的参数中有其他的参数导致dump失败,排查看是否有如下参数,如果有去掉即可
-XX:+DisableExplicitGC
  • 通过JDK自带的工具jmap获取dump文件;
# 导出内存dump文件
jmap -F -dump:live,file=jmap.hprof [PID] 
  • dump文件从线上主机下载到本地;
# 命令格式:  
scp local_file remote_username@remote_ip:remote_folder  
或者
scp local_file remote_username@remote_ip:remote_file  
或者
scp local_file remote_ip:remote_folder  
或者
scp local_file remote_ip:remote_file
  • 通过JDK自带的jvisualvm工具分析或者下载三方软件jprofiler来分析dump文件即可;
  • 最重要的一点,要把 fullgc 发生时刻的 dump 文件和正常没有发生 fullgc 时间的 dump 文件都下载到本地,然后对比观察分析方便找到问题原因

17、Full GC效果不好 每次只能从90%-》85%之后又90%了,这种情况下应该怎么办比较好?

答:

  • 如果是一次fullgc后,剩余对象不多。那么说明你eden区设置太小,导致短生命周期的对象进入了old区。
  • 如果一次fullgc后,old 区回收率不大,那么说明old区太小。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐