Java垃圾回收机制及常见垃圾收集器

一、内存泄露

Java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”。 

二、如何判断无用对象

1、引用计数法(Reference Counting Collector)

引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。 

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。 

缺点:无法检测出循环引用。无法回收被循环引用的对象。 

OC中的内存管理有:1、手动内存管理和自动释放池 MRC 2、自动内存管理(编译器特性) ARC 3、自动垃圾回收 GC。对于IOS,使用weak(IOS5以上-ARC中使用)和unsafe_unretained(IOS5以下-MRC中使用)两个关键字来处理引用计数中的的循环引用问题。参考:__weak & __unsafe_unretained的用法以及区别

2、可达性分析法-根搜索算法

可达性分析法也叫根搜索算法。根搜索算法是从离散数学中的图论引入,程序把所有的引用关系看作一张图,从一个GC ROOT节点开始,寻找对应的引用节点。找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余节点则被认为是没有被引用的节点,即无用节点。 

java中可作为GC Root的对象有

  1. 虚拟机栈中的引用对象(本地变量表)
  2. 方法区中静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中引用的对象(Native对象)

三、垃圾收集算法

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

Java垃圾回收机制及常见垃圾收集器_第1张图片

标记清除算法是最基础的一种收集算法,采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但是由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。 

2、复制算法(Copying)

Java垃圾回收机制及常见垃圾收集器_第2张图片

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成一个对象面和多个空闲面,程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾收集器就从根节点扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行。 

3、标记-整理算法(Mark-Compact)

Java垃圾回收机制及常见垃圾收集器_第3张图片

标记-整理算法的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。在基于Mark-Compact算法的收集器的实现中,一般增加句柄和句柄表。 

4、分代收集算法(Generational Collection)

Java垃圾回收机制及常见垃圾收集器_第4张图片

当前商用虚拟机的垃圾收集都采用“分代收集”算法。
分代的垃圾回收策略,是基于这样一个简单实事:不同的对象生命周期是不一样的。因此,不同生命周期的对象可以采用不同的回收算法,以便提高回收效率。
新生代多采用复制算法。老年代中因为对象存活率高,没有额外空间对它进行分配担保,多使用“标记-清除”或“标记-整理”算法来进行回收。 

年轻代(Young Generation)

1、所有新生成的对象首先都放在年轻代中。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。 

2、新生代内存默认按照8:1:1的比例分为一个eden区和两个 survivor (survivor0 即 From Space,survivor1 即 To Space) 区。大部分对象在Eden区中生成。回收时先将Eden区存活对象复制到 survivor0 区,然后清空 Eden 区,当这个 survivor0 区也存放满了时,则将 Eden 区和 survivor0 区中存活对象复制到另一个 survivor1 区,然后清空 Eden 区 和 survivor0 区,此时 survivor0 区是空的,然后将 survivor0 区 和 survivor1 区交换,即保持 survivor1 区为空,如此往复。

3、当 survivor1 区不足以存放 Eden 和 survivor0 的存活对象时,就将存活的对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。

4、新生代发生的GC也叫做 Minor GC,Minor GC 发生的频率比较高(不一定等Eden区满了才触发)

5、总结:新建对象都是用新生代分配内存,Eden 空间不足时,会把存活的对象转移到 Survivor0 中,Survivor0 空间不足,会复制 Eden 和 Survivor0 中存活的对象到Survivor1中,并紧密的排列好。先清空 Eden 和 Survivor0 ,再交换 Survivor0 和 Survivor1,保持 Survivor1 的空闲。新生代的大小可以由 -Xmn 来控制。 Eden 和 Survivor 的空间比例可以由-XX:SurvivorRatio 来控制,默认是8:1:1。

年老代(Old Generation)

1、在年轻代中经历了N次垃圾回收后,仍然存活的对象,就会被存到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

2、内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发 Major GC 即 Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

持久代(Permanent Generation、永久代)

1、用于存放静态文件,如Java类,方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些 class,例如 Hibernate 等,在这种时候需要设置一个比较大的挺久代空间来存放这些运行过程中新增的类。 

2、永久代在JDK8中被完全的移除了。所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。 

3、在JDK8中,class metadata(the virtual machines internal presentation of Java class),被存储在叫做 Metaspace 的 native memory中。它是一个可以自动扩容与自动压缩的动态内存空间。一些新的 flags 被加入,如-XX:MetaspaceSize(Metaspace初始大小)、-XX:MaxMetaspaceSize(最大空间),-XX:MinMetaspaceFreeRatio(空间扩容阀值百分比),-XX:MaxMetaspaceFreeRatio(空间压缩阀值百分比)。

三、垃圾收集器

如何评估一个垃圾收集算法?

两个术语

吞吐量(throughput)
(最大)暂停时间(pause time) 

评估一个垃圾收集算法的两个标准

1、吞吐量越高算法越好
2、最大暂停时间越短算法越好 

并发和并行(并发收集器,并行收集器)

并行(Parallel): 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent): 指用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。 

常见的垃圾收集器

新生代常用收集器:Serial、PraNew、Parallel Scavenge
老年代常用收集器:Serial Old、Parallel Old、CMS
为解决CMS算法产生空间碎片和其它一系列问题缺陷,HotSpot提供了另外一种垃圾回收策略,G1(Garbage First)算法,通过参数-XX:+UseG1GC来启用,该算法在JDK7u14版本被正式推出,主要应用在多CPU大内存的服务中,后续会替换掉CMS垃圾收集器。
Parallel Scavenge收集器和G1收集器都没有使用传统的GC收集器代码框架,而另外独立实现。其余几种收集器则共用了部分的框架代码。 

HotSpot垃圾收集器

JDK 1.7 Update 14 之后的HotSpot虚拟机的垃圾收集器如下图。
存在连线的表示可以搭配使用。 

Java垃圾回收机制及常见垃圾收集器_第5张图片

Serial 收集器(单线程)

Java垃圾回收机制及常见垃圾收集器_第6张图片

1、最基本、发展历史最悠久,在JDK 1.3之前是新生代收集的唯一选择。
2、是一个单线程(只会使用一个收集线程,且必须暂停所有工作线程,即stop the world)的收集器,新生代采用的复制算法,老年代采用标记-整理算法。
3、简单高效(没有线程交互的开销),现在依然是虚拟机运行在Client模式下的默认新生代收集器。
4、Serial Old是Serial收集器的老年代版本。它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是被Client模式下的虚拟机使用。在Server模式下,它主要还有两大用途:一个是在JDK1.5及以前的版本中与 Parallel Scavenge 收集器搭配使用,另外一个就是作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 的时候使用。
5、通过指定 -XX:+UseSerialGC 参数,使用 Serial + Serial Old 的串行收集器的组合进行内存回收。

ParNew收集器(多线程)

Java垃圾回收机制及常见垃圾收集器_第7张图片

1、ParNew新生代收集器,是Serial新生代收集器的多线程实现,垃圾回收的时候依然会stop the world,只是相比Serial收集器而言会运行多条线程进行垃圾回收。 采用的也是复制算法。
2、ParNew收集器在单CPU环境中绝对不会有比Serial收集器更好的效果。
3、是许多运行在Server模式下虚拟机首先的新生代收集器,重要原因就是除了Serial收集器外,只有它能与CMS收集器配合工作。
4、默认开启的收集线程数与CPU的数量相同,在CPU非常多时,可使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
5、-XX:+UseParNewGC,打开此开关后,使用ParNew + Serial Old 的收集器组合进行内存回收,这样新生代使用并行收集器,老年代使用串行收集器。 

Parallel Scavenge 收集器(多线程,吞吐量优化收集器)

Java垃圾回收机制及常见垃圾收集器_第8张图片

1、Parallel Scavenge 是采用复制算法的多线程新生代垃圾回收器,似乎和ParNew有点类似。
2、Parallel Scavenge 收集器的特点是它的关注点与其它收集器不同。CMS等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控的吞量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗的时间比值,即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
3、停顿时间越短就越适合需要与用户交互的程序。良好的响应速度能提升用户的体验(如CMS),而高吞吐量则可以最高效率地利用CPU时间,快地完成程序的运算任务。故 Parallel Scavenge 收集器尽主要适合在后台运算而不需要太多交互的任务。
4、Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMills 参数及直接设置吞吐量大小的-XX:GCTimeRatio参数。
5、Parallel Scavenge 收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得注意。当这个参数打开之后,就不需要手工指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio),晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
6、自适应调节策略(GC Ergonomics)也是Parallel Scavenge 收集器与ParNew收集器的一个重要区别。
7、Parallel Old 是 Parallel Scavenge 收集器的老年代版本,采用多线程和“标记-整理”算法。这个收集器是在jdk1.6中才开始提供的,在此之前,新生代的Parallel Scavenge 收集器一起处于比较尴尬的状态。原因是如果新生代Parallel Scavenge收集器,那么老年代除了 Serial Old(Mark-Sweep)收集器外别无选择。由于单线程的老年代 Serial Old 收集器在服务端应用性能上的“拖累”,即使使用了Parallel Scavenge 收集器也未必能在整体应用上获得吞吐量最大化的效果,又因为老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合吞吐量甚至还不如ParNew + CMS的组合“给力”。直到 Parallel Old 收集出现后,“吞吐量优先”收集器终于有了比较名副其实的应用,在注重吞吐量及CPU资源敏感场合,都可以优先考虑 Parallel Scavenge + Parallel Old 收集器。
8、Parallel Scavenge 与 Parallel Old 都会暂停所有用户线程(即STW)。 

参数 垃圾收集器组合 备注
-XX:+UseParallelGC Parallel Scavenge + Serial Old 虚拟机运行在Server模式下的默认值,新生代并行 + 老年代串行
-XX:+UseParallelOldGC Parallel Scavenge + Parallel Old 新生代并行 + 老年代并行,该参数在JDK1.5之后已弃用

CMS(Concurrent Mark Sweep)收集器(并发)

Java垃圾回收机制及常见垃圾收集器_第9张图片

简述

HotSpot JVM 的 CMS 收集器主要目标就是:低应用停顿时间。该目标对于大多数交互式应用很重要,比如 web 应用。吞吐量收集器可能暂停应用程序线程很长一段时间,这使用吞吐量收集器能安全的进行垃圾回收。相比之下,CMS 收集器被设计成在大多数时间能与应用程序线程并行执行,仅会有一点(短暂的)停顿时间。GC 与 应用程序并行执行的缺点就是,可能出现线程同步带来的数据不一致问题。为了实现安全且正确的并发执行,CMS收集器的GC周期被分了好几个连续的阶段。CMS 采用标记-清除算法,在 JVM 参数加上 -XX:+UseConcMarkSweepGC 来启用。 

CMS 过程

1、初始标记(STW initial mark)
2、并发标记(Concurrent marking)
3、并发预清理(Concurrent precleaning)
4、重新标记(STW remark)
5、并发清理(Concurrent sweeping)
6、并发重置(Concurrent reset) 

过程详述

初始标记: 在这个阶段,需要虚拟机暂停所有正在执行的任务,官方的叫法STW(Stop The World)。这个过程从垃圾回收的“根对象”开始,只扫描能够和“根对象”直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
并发标记: 这个阶段紧随初始标记阶段,在初始标记的基础上继续躺下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
并发预清理: 并发预清理阶段仍然是并发的。在这个阶段,虚所机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代,或者有一些对象被分配到老年代)。通过重新扫描,减少下一阶段“重新标记”的工作,因为下一阶段会 Stop The World。
重新标记: 这个阶段会暂停虚拟机,GC线程扫描在CMS堆中剩余的对象。扫描从“根对象”开始向下追溯,并处理对象关联。
并发清理: 清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
并发重置: 这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。 

参数 垃圾收集器组合 备注
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC(新版默认开启) ParNew + CMS(Serial Old为替补) 新生代并行 + 老年代并行(串行替补)
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC Serial + CMS 新生串行 + 老年代并行

CMS的优点

并发收集、低停顿 

CMS的缺点

1) CMS 收集器对 CPU 资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用一部分线程而导致应用程序变慢,总吞吐量会降低。CMS 默认启动的回收线程数是(CPU 数量 + 3) / 4 ,也就是当CPU在4个以上时,并发回收时垃圾收集线程最多占用不超过 25% 的 CPU 资源。但是当 CPU 不足4个时,那么 CMS 对用户程序的影响就可能变得很大,如果 CPU 负载本来就比较大的时候,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低 50% ,这也很让人受不了。 

2) CMS 收集器无法处理浮动垃圾(Floating Garbage),可能会出现“Concurrent Mode Failure (并发模式故障) ”失败而导致 Full GC 产生。由于 CMS 并发清理阶段,用户线程还在运行着,伴随程序的运行自然还会有新的垃圾不断产生,这一部分出现在标记过程之后,CMS无法在本次收集中处理掉它们,啥那留待下一次 GC 时再将其清理掉。这一部分垃圾就称为“浮动垃圾”。也是由于垃圾收集阶段用户线程还需要运行,即还需要预留足够的内存空间给用户线程使用,因此 CMS 收集器不能像其它收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。在默认设置下,CMS 收集器在老年代使用了 68% 的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数 -XX:CMSInitiatingOccupancyFraction 的值来提高触发百分比,以便降低内存回收次数收获取更好的性能。要是 CMS 运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案;临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数 -XX:CMSInitiatingOccupancyFraction 设置得太高将会很容易导致大量“Concurrent Module Failure”失败,性能反而降低。 

3) CMS 是一款“标记-清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次 Full GC。为了解决这个问题,CMS收集器提供了一个 -XX:+UseCMSCompactAtFullCollection 开头参数,用于在“享受”完Full GC 服务之后额外免费附送一个碎片整理过程,内存整理的过程是无法并发的。空间碎片问题没有了,但停顿的时间不得不变长了。 

总得来说,CMS 减少了回收的停顿时间,更是降低了堆空间的利用率。 

补充

-XX:+CMSClassUnloadingEnabled 相对于并行收集器,CMS收集器并不会对永久代进行垃圾回收。如果希望对永久代进行垃圾回收,可以设置标记 -XX:+CMSClassUnloadingEnabled 。注意,即使没有设置这个标志,一旦永久代空间耗尽,JVM也会尝试进行垃圾回收,但是收集不会是并行的,而是进行一次 Full GC。
CMS GC 会在特殊情况(JVM认为内存不够了 Concurrent Mode Failure,Promotion Fail)暂停所有用户线程去做 MSC(Serial Old,单线程,Mark-Sweep-Compact,Full GC)。 

什么时候使用 CMS

如果你的应用程序对停顿比较敏感,并且在应用程序运行的时候可以提供更大的内存和更多的 CPU (也就是硬件牛逼),那么使用 CMS 来收集会给你带来好处。还有,如果在 JVM 中,有相对较多存活时间较长的对象(老年代比较大)会更适合使用 CMS。 

G1收集器(Garbage First)

简介

G1 是一种并发、并行、部分 Stop The World、使用 Copying 算法收集的分代的增量式收集器。 

与 Hotspot 之前的 Serial、Parallel、CMS 等收集器不同的是,G1 将堆分为很多大小相等的 Region,每次收集时会判断各个 Region 的活性-即垃圾对象的占比,垃圾对象占比越多的 Region 回收的收益越大,然后 G1 会按照设置的停顿时间目标、前几次回收 Region 所用时间来估算要回收哪些 Region,即用最小的时间获取最大的收益,这也是 Garbage First 名字的含义。 

Garbage First Collector的使命是在未来替换 CMS,并且在 JDK1.9 已经成为默认的收集器。 

为什么需要G1

在服务端更注重的是短停顿时间,也就是stop-the-world的时候,另外一段早教课内的总停顿时间也是一个衡量指标。 

Mark-Sweep,Mark-Compact均需要和清理区域大小成比例的工作量,而Copying算法则需要一般是一半的空间用于存放copy的活对象。CMS 的Initial Marking 和 Remarking 两个 STW 阶段在 Heap 区越来越大的情况下需要的时间越长,并且由于内存碎片,需要压缩的话,也会造成较长的停顿时间。所以需要一种高吞吐量的短暂停时间的收集器,而不管堆内存多大。 

G1的特点

将 Heap 分为大小相等的 Region ,逻辑分代,Marking 的大部分是并发的,STW 中部分采取多线程并行执行,采用 Copying 进行多线程收集。 

推荐使用G1的场景

个人认为更换GC或者进行调优只能算是系统的锦上添花,并不能作为主要解决系统性能问题的关键,出现内存问题时,应当以修改应用代码为主、编写清晰的GC友好的代码,选择与应用场景合适的收集器可以提高系统的性能。
现在推荐从CMS更换到G1的一些情况如下: 

Java堆的50%以上都是活对象
对象的分配速率变化很大
由于 old gc 或者压缩导致不可忍受的长时间的暂停 

官方描述的使用场景

1、能够如同CMS一样,垃圾收集线程和应用线程并发执行 

2、压缩空闲空间不会导致冗长的暂停时间 

3、需要可预测的GC暂停时间 

4、不希望牺牲太多的吞吐量性能 

5、不需要很大的Java堆内存 

堆内存结构

1、以往的垃圾回收算法,如 CMS,使用的堆内存结构如下:

新生代:eden space + 2 个 survivor
老年代:old space
持久代:1.8之前的 perm space
元空间:1.8之后的 metaspace 

2、在 G1 算法中,采用了另外一种完全不同的方式组织堆内存,堆内存被划分为多个大小相等的内存块(Region),每个 Region 是逻辑连续的一段内存,结构如下:

Java垃圾回收机制及常见垃圾收集器_第10张图片

每个Region被标记了 E、S、O和H,说明每个 Region 在运行时都充当了一种角色,其中H是以往算法中没有的,它代表 Humogous,这表示这些 Region 存储的是巨型对象(humongous object,H-obj),当新建对象大小超过 Region 大小一半时,直接在新的一个或多个连续 Region 中分配,并标记为H。 

Region

堆内存中一个 Region 的大小可以通过 -XX:G1HeapRegionSize 参数指定,大小区间只能是 1M、2M、4M、8M、16M和32M,总之是2的幂次方。 

GC模式

G1中提供了两种垃圾回收模式,young gc、mixed gc
Allocation Failure 异常时,会执行 full gc,即单线程的 serial old gc。会导致长时间的暂停时间。 

Young Garbage Collection

当 Eden 区域无法申请新的对象时(满了),就会进行 Young GC,Young GC 将 Eden 和 Survivor 区域的 Region(称为 Collection Set,CSet)中的活对象 Copy 到一些新 Region 中(即新的 Survivor),当对象的 GC 年龄达到阈值后 Copy 到 Old Region 中。由于采取的是 Copying 算法,所以就避免了内存碎片问题,不再需要单独的压缩。 

Mixed Garbage Collection

当old 区 Heap 的对象占总 Heap 的比例超过 InitiatingHeadOccupancyPercent 之后,就会开始 Concurent Marking,完成了 Concurrent Marking后,G1会从 Young GC 切换到 Mixed GC。 选定所有年轻代里的 Region,外加根据 global concurrent marking 统计得出收集收益高的若干老年代 Region。在用户指定的开销目标范围内尽可能选择收益高的老年代 Region。 

由上面描述可知,Mixed GC 不是 full GC,它只能回收部分老年代的 Region,如果 mixed GC 实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行 Mixed GC,就会使用 serial old GC(full GC)来收集整个 GC Heap。所以我们可以知道,G1 是不提供 full GC的。 

global concurrent marking

上文中,多次提到了 global concurrent marking,它的执行过程类似 CMS,但是不同的是,在 G1 GC 中,它主要是为 Mixed GC 提供标记服务的,并不是一次 GC 过程的一个必须环节。其执行过程分为四个步骤: 

初始标记(initial mark,STW)。它标记了从GC Root 开始直接可达的对象。
并发标记(Concurrent Marking)。这个阶段从 GC Root 开始对 Head 中的对象标记,标记线程与应用程序线程并行执行,并且收集各个 Region 的存活对象信息。
最终标记(Remark,STW)。标记那些在并发标记阶段发生变化的对象,将被回收。
清除垃圾(Cleanup)。清除空 Region(没有存活对象的 Region)加入到 free list。 

第一阶段 initial mark 是共用了 Young GC 的暂停,这是因为他们可以复用 root scan 操作,所以可以说 global concurrent marking 是伴随 Young GC而发生的。第四阶段 Cleanup 只是回收了没有存活对象的 Region,所以它并不需要 STW。 

Full GC

和CMS一样,G1 的一些收集过程是和应用程序并发执行的,所以可能还没有回收完成,就由于申请内存的速度比回收速度快,新的对象就占满了所有空间,在 CMS 中叫做 Concurrent Mode Failure,在 G1 中称为 Allocation Failure,也会降级为一个 STW 的 full gc。 

Floating Garbage

G1使用一种 Snapshot-At-The-Begining 的方式记录活对象,也就是那一时刻(整个堆 concurrent marking 开始的时候)的内存的 Object graph,但是在这之后,这里面的对象可能会变成 Garbage,叫做 floating garbage,只能等待下一次垃圾回收时再回收掉。 

补充

  1. 一款面向服务端应用的垃圾收集器,后续会替换掉CMS垃圾收集器。
  2. 将堆分为大小相等的独立区域,避免全区域的垃圾收集;新生代和老年代不再物理隔离,只是部分Region的集合;
  3. G1跟踪各个Region垃圾堆积的价值大小,在后台维护一个优先列表,根据允许的收集时间优先回收价值最大的Region;
  4. Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,采用Remembered Set来避免全堆扫描;

四、参考网址

深入理解 Java 垃圾回收机制:http://www.importnew.com/16173.html
Java GC基本算法
详解Java GC的工作原理+Minor GC、FullGC
初识JVM GC原理
常见的垃圾收集器
什么是HotSpot VM & 深入理解Java虚拟机 JVM
G1垃圾收集器介绍
深入探究 JVM | Safepoint 及 GC 的触发条件
JVM 垃圾回收算法及回收器详解
jvm内存很大,cms报错concurrent mode failure
简介JVM的Parallel Scavenge及Parallel Old垃圾收集器
Parallel Scavenge收集器
了解CMS(Concurrent Mark-Sweep)垃圾回收器
JVM实用参数(六) 吞吐量收集器
CMS 收集器
Java GC系列(3):垃圾回收器种类
系统最常用的CMS GC mode——ParNew & CMS(Serial Old作为替补)(heap> 5g)
JVM GC
JVM Full GC对于年轻代的收集算法与Young GC的收集算法是一样的吗?
JVM内存管理------垃圾搜集器参数精解
JAVA虚拟机之三:CMS垃圾收集器
FAQ-GC
Java Hotspot G1 GC的一些关键技术
Garbage First G1收集器 理解和原理分析
Java 8 垃圾收集调优指南2-分代(译)
G1 垃圾收集器介绍
什么是G1垃圾回收算法
深入探究 JVM | 初探 GC 算法
java内存回收机制
Java中的垃圾回收机制浅析

五、参考书籍

《深入理解 Java 虚拟机》

 

你可能感兴趣的:(内存优化,android,Java垃圾回收机制,jvm,gc,垃圾收集器,内存)