JAVA GC(垃圾回收机制)面试讲解

1.什么是GC?

大白话说就是垃圾回收机制,内存空间是有限的,你建立的每一个对象和变量都会占据内存,gc作的就是对象清除将内存释放出来,这就是GC要作的事。java

2.须要GC的区域

提及垃圾回收的场所,了解过JVM(Java Virtual Machine Model)内存模型的朋友应该会很清楚,堆是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区。算法

3.堆内存的结构

Java将堆内存分为3大部分:新生代、老年代和永久代,其中新生代又进一步划分为Eden、S0、S1(Survivor)三个区bootstrap

4.堆内存上对象的分配与回收:

咱们建立的对象会优先在Eden分配,若是是大对象(很长的字符串数组)则能够直接进入老年代。虚拟机提供一个
-XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝。数组

另外,长期存活的对象将进入老年代,每一次MinorGC(年轻代GC),对象年龄就大一岁,默认15岁晋升到老年代,经过
-XX:MaxTenuringThreshold设置晋升年龄。多线程

堆内存上的对象回收也叫作垃圾回收,那么垃圾回收何时开始呢?并发

垃圾回收主要是完成清理对象,整理内存的工做。上面说到GC常常发生的区域是堆区,堆区还能够细分为新生代、老年代。新生代还分为一个Eden区和两个Survivor区。垃圾回收分为年轻代区域发生的Minor GC和老年代区域发生的Full GC,分别介绍以下。性能

Minor GC(年轻代GC):
对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,由于Java大多数对象都是朝生夕灭,因此Minor GC很是频繁,并且速度也很快。spa

Full GC(老年代GC):
Full GC是指发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC通常都会有一次Minor GC。线程

接下来,咱们来看关于内存分配与回收的两个重要概念吧。3d

动态对象年龄断定:

若是Survivor空间中相同年龄全部对象的大小总和大于Survivor空间的一半,那么年龄大于等于该对象年龄的对象便可晋升到老年代,没必要要等到-XX:MaxTenuringThreshold。

空间分配担保:

发生Minor GC时,虚拟机会检测以前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小。若是大于,则进行一次Full GC(老年代GC),若是小于,则查看HandlePromotionFailure设置是否容许担保失败,若是容许,那只会进行一次Minor GC,若是不容许,则改成进行一次Full GC

5.目前会问到的问题

1.年轻代三个区比例

Eden,S0,S1比例8:1:1

2.为何要有Survivor区

这个我用别人说的话解释一下:

连接:https://www.jianshu.com/p/2caad185ee1f

为何须要Survivor空间。咱们看看若是没有 Survivor 空间的话,垃圾收集将会怎样进行:一遍新生代 gc 事后,无论三七二十一,活着的对象所有进入老年代,即使它在接下来的几回 gc 过程当中极有可能被回收掉。这样的话老年代很快被填满, Full GC 的频率大大增长。咱们知道,老年代通常都会被规划成比新生代大不少,对它进行垃圾收集会消耗比较长的时间;若是收集的频率又很快的话,那就更糟糕了。基于这种考虑,虚拟机引进了“幸存区”的概念:若是对象在某次新生代 gc 以后任然存活,让它暂时进入幸存区;之后每熬过一次 gc ,让对象的年龄+1,直到其年龄达到某个设定的值(好比15岁), JVM 认为它颇有多是个“老不死的”对象,再呆在幸存区没有必要(并且总是在两个幸存区之间反复地复制也须要消耗资源),才会把它转移到老年代。

Survivor的存在乎义,就是减小被送到老年代的对象,进而减小Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

3.为何有两个Survivor区

为何 Survivor 分区不能是 1 个?

若是 Survivor 分区是 1 个的话,假设咱们把两个区域分为 1:1,那么任什么时候候都有一半的内存空间是闲置的,显然空间利用率过低不是最佳的方案。

但若是设置内存空间的比例是 8:2 ,只是看起来彷佛“很好”,假设新生代的内存为 100 MB( Survivor 大小为 20 MB ),如今有 70 MB 对象进行垃圾回收以后,剩余活跃的对象为 15 MB 进入 Survivor 区,这个时候新生代可用的内存空间只剩了 5 MB,这样很快又要进行垃圾回收操做,显然这种垃圾回收器最大的问题就在于,须要频繁进行垃圾回收。

为何 Survivor 分区是 2 个?

刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程很是重要,由于这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,而后下一轮S0与S1交换角色,如此循环往复。若是对象的复制次数达到16次,该对象就会被送到老年代中。下图中每部分的意义和上一张图同样,就不加注释了。
两块Survivor避免碎片化
上述机制最大的好处就是,整个过程当中,永远有一个survivor space是空的,另外一个非空的survivor space无碎片

那么,Survivor为何不分更多块呢?比方说分红三个、四个、五个?显然,若是Survivor区再细分下去,每一块的空间就会比较小,很容易致使Survivor区满

总结

根据上面的分析能够得知,当新生代的 Survivor 分区为 2 个的时候,不管是空间利用率仍是程序运行的效率都是最优的,因此这也是为何 Survivor 分区是 2 个的缘由了。

6. JVM如何断定一个对象是否应该被回收?(重点掌握)

 判断一个对象是否应该被回收,主要是看其是否还有引用。判断对象是否存在引用关系的方法包括引用计数法以及可达性分析

引用计数法:

是一种比较古老的回收算法。原理是此对象有一个引用,即增长一个计数,删除一个引用则减小一个计数。垃圾回收时,只须要收集计数为0的对象。此算法最致命的是没法处理循环引用的问题。

可达性分析

可达性分析的基本思路就是经过一系列能够作为root的对象做为起始点,从这些节点开始向下搜索。当一个对象到root节点没有任何引用连接时,则证实此对象是能够被回收的。如下对象会被认为是root对象:

  • 栈内存中引用的对象 
  • 方法区中静态引用和常量引用指向的对象 
  • 被启动类(bootstrap加载器)加载的类和建立的对象
  • Native方法中JNI引用的对象。 

7. JVM垃圾回收算法有哪些?

HotSpot 虚拟机采用了可达性分析来进行内存回收,常见的回收算法有标记-清除算法,复制算法和标记整理算法。

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

标记-清除算法执行分两阶段。

第一阶段:从引用根节点开始标记全部被引用的对象,

第二阶段:遍历整个堆,把未标记的对象清除。此算法须要暂停整个应用,而且会产生内存碎片。

 

 

 缺点:

  • 执行效率不稳定,会由于对象数量增加,效率变低
  • 标记清除后会有大量的不连续的内存碎片,空间碎片太多就会致使没法分配较大对象,没法找到足够大的连续内存,而发生gc

复制算法:

复制算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。复制算法每次只处理正在使用中的对象,所以复制成本比较小,同时复制过去之后还能进行相应的内存整理,不会出现“碎片”问题。固然,此算法的缺点也是很明显的,就是须要两倍内存空间。

 

 

 缺点:

  • 可用内存缩成了一半,浪费空间

标记-整理算法:

标记-整理算法结合了“标记-清除”和“复制”两个算法的优势。也是分两阶段,

第一阶段从根节点开始标记全部被引用对象,

第二阶段遍历整个堆,清除未标记对象而且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

 

 

 

8.垃圾收集器(掌握CMS和G1)

JVM中的垃圾收集器主要包括7种,即Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old以及CMS,G1收集器。以下图所示:

 

 

一、Serial收集器:

Serial收集器是一个单线程的垃圾收集器,而且在执行垃圾回收的时候须要 Stop The World。虚拟机运行在Client模式下的默认新生代收集器。Serial收集器的优势是简单高效,对于限定在单个CPU环境来讲,Serial收集器没有多线程交互的开销。

二、Serial Old收集器:

Serial Old是Serial收集器的老年代版本,也是一个单线程收集器。主要也是给在Client模式下的虚拟机使用。在Server模式下存在主要是作为CMS垃圾收集器的后备预案,当CMS并发收集发生Concurrent Mode Failure时使用。

三、ParNew收集器:

ParNew是Serial收集器的多线程版本,新生代是并行的(多线程的),老年代是串行的(单线程的),新生代采用复制算法,老年代采用标记整理算法。可使用参数:-XX:UseParNewGC使用该收集器,使用 -XX:ParallelGCThreads能够限制线程数量。

四、Parallel Scavenge垃圾收集器:

Parallel Scavenge是一种新生代收集器,使用复制算法的收集器,并且是并行的多线程收集器。Paralle收集器特色是更加关注吞吐量(吞吐量就是cpu用于运行用户代码的时间与cpu总消耗时间的比值)。能够经过-XX:MaxGCPauseMillis参数控制最大垃圾收集停顿时间;经过-XX:GCTimeRatio参数直接设置吞吐量大小;经过-XX:+UseAdaptiveSizePolicy参数能够打开GC自适应调节策略,该参数打开以后虚拟机会根据系统的运行状况收集性能监控信息,动态调整虚拟机参数以提供最合适的停顿时间或者最大的吞吐量。自适应调节策略是Parallel Scavenge收集器和ParNew的主要区别之一。

五、Parallel Old收集器:

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。

六、CMS(Concurrent Mark Sweep)收集器(并发标记清除)

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于标记-清除算法实现的,是一种老年代收集器,一般与ParNew一块儿使用。

CMS的垃圾收集过程分为4步:

  • 初始标记:须要“Stop the World”,初始标记仅仅只是标记一下GC Root能直接关联到的对象,速度很快。
  • 并发标记:是主要标记过程,这个标记过程是和用户线程并发执行的。
  • 从新标记:须要“Stop the World”,为了修正并发标记期间因用户程序继续运做而致使标记产生变更的那一部分对象的标记记录(停顿时间比初始标记长,但比并发标记短得多)。
  • 并发清除:和用户线程并发执行的,基于标记结果来清理对象。

 

 

 

那么问题来了,若是在从新标记以前恰好发生了一次MinorGC,会不会致使从新标记阶段Stop the World时间太长?

答:不会的,在并发标记阶段其实还包括了一次并发的预清理阶段,虚拟机会主动等待年轻代发生垃圾回收,这样能够将从新标记对象引用关系的步骤放在并发标记阶段,有效下降从新标记阶段Stop The World的时间。

CMS垃圾回收器的优缺点分析:

CMS以下降垃圾回收的停顿时间为目的,很显然其具备并发收集,停顿时间低的优势。

缺点主要包括以下:

  • 对CPU资源很是敏感,由于并发标记和并发清理阶段和用户线程一块儿运行,当CPU数变小时,性能容易出现问题。
  • 收集过程当中会产生浮动垃圾,因此不能够在老年代内存不够用了才进行垃圾回收,必须提早进行垃圾收集。经过参数-XX:CMSInitiatingOccupancyFraction的值来控制内存使用百分比。若是该值设置的过高,那么在CMS运行期间预留的内存可能没法知足程序所需,会出现Concurrent Mode Failure失败,以后会临时使用Serial Old收集器作为老年代收集器,会产生更长时间的停顿。
  • 标记-清除方式会产生内存碎片,可使用参数-XX:UseCMSCompactAtFullCollection来控制是否开启内存整理(没法并发,默认是开启的)。参数-XX:CMSFullGCsBeforeCompaction用于设置执行多少次不压缩的Full GC后进行一次带压缩的内存碎片整理(默认值是0)。

接下来,咱们先看下上边介绍的浮动垃圾是怎么产生的吧。

浮动垃圾:

因为在应用运行的同时进行垃圾回收,因此有些垃圾可能在垃圾回收进行完成时产生,这样就形成了“Floating Garbage”,这些垃圾须要在下次垃圾回收周期时才能回收掉。因此,并发收集器通常须要20%的预留空间用于这些浮动垃圾。

七、G1(Garbage-First)收集器:

G1收集器将新生代和老年代取消了,取而代之的是将堆划分为若干的区域,每一个区域均可以根据须要扮演新生代的Eden和Survivor区或者老年代空间,仍然属于分代收集器,区域的一部分包含新生代,新生代采用复制算法,老年代采用标记-整理算法。

经过将JVM堆分为一个个的区域(region),G1收集器能够避免在Java堆中进行全区域的垃圾收集。G1跟踪各个region里面的垃圾堆积的价值大小(回收所得到的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据回收时间来优先回收价值最大的region。

G1收集器的特色:

  • 并行与并发:G1能充分利用多CPU,多核环境下的硬件优点,来缩短Stop the World,是并发的收集器。
  • 分代收集:G1不须要其余收集器就能独立管理整个GC堆,可以采用不一样的方式去处理新建对象、存活一段时间的对象和熬过屡次GC的对象。
  • 空间整合:G1从总体来看是基于标记-整理算法,从局部(两个Region)上看基于复制算法实现,G1运做期间不会产生内存空间碎片。
  • 可预测的停顿:可以创建能够预测的停顿时间模型,预测停顿时间。

和CMS收集器相似,G1收集器的垃圾回收工做也分为了四个阶段:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

其中,筛选回收阶段首先对各个Region的回收价值和成本进行计算,根据用户指望的GC停顿时间来制定回收计划。

9.Java经常使用版本垃圾收集器

1.首先说若是看怎么看

个人版本是jdk1.8

java -XX:+PrintCommandLineFlags -version

2.jdk1.8和1.9用的版本

jdk1.8默认的新生代垃圾收集器:Parallel Scavenge,老年代:Parallel Old

jdk1.9 默认垃圾收集器G1

 

相关文章
相关标签/搜索