java垃圾收集器 - JVM垃圾收集笔记整理

目录

在谈垃圾收集器前先简单的大致了解下垃圾收集算法

标记清除算法

复制算法

标记整理算法/标记压缩算法

分代算法

分区算法

Minor GC、Full GC触发条件

不同的垃圾收集器

1. Serial收集器

2. ParNew收集器

3. Parallel Scavenge收集器

4. Serial Old收集器

5. Paralled Old收集器

6. CMS收集器

7. G1收集器


在谈垃圾收集器前先简单的大致了解下垃圾收集算法

  • 标记清除算法

       分为两个阶段的动作。第一、标记所有从根节点开始可达的对象。第二、清除所有未标记的对象。缺点:产生大量不连续的内存碎片,空间碎片太多可能会导致后期JVM需要分配大对象时,无法找到足够连续内存空间,从而再次触发一次垃圾收集动作。

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

  • 复制算法

       需要两块内存空间,只使用其中一块。开始回收的时候将已使用的一块内存空间中存活对象复制到另一块内存空间,并一次性回收这块内存空间。缺点:会有一定的内存空间浪费

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

  • 标记整理算法/标记压缩算法

       有点像是整合了标记清除算法和复制算法。标记可回收对象,然后复制所有存活对象像一端移动。最后直接清理掉边界外的内存。

 

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

  • 分代算法

       内存区域根据对象存活周期划分为几块。新生代和老年代。新生代只有少量存活对象,使用复制算法。老年代因为对象存活率较高,使用标记清理或者标记整理算法。

 

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

  • 分区算法

        按照对象生命周期长短划分成两个部分,将整个堆空间划分成连续的不同小区间。每一个小区间(Region)都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间(Region)。从而减少(Stop-The-World时间STW)停顿时间。

学习来源:《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

Minor GC、Full GC触发条件


Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

内容来源:https://blog.csdn.net/u013630349/article/details/78342645

不同的垃圾收集器

1. Serial收集器

    新生代收集器,历史悠久的垃圾收集器,曾经是JDK1.3.1之前新生代收集的唯一选择。

是一个单线程的收集器。需要暂停其他所有工作线程(用户操作线程也需要一并停止Stop-The-World,缩写STW。本文后面会使用STW代表Stop-TheWorld),直到垃圾收集的结束。

使用-XX:+UseSerialGC参数可指定新生代Serial收集器和老年代Serial Old收集器。

client模式下默认

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

2. ParNew收集器

    新生代收集器,其实就是Serial收集器的多线程版本。在单CPU环境中不会比Serial收集器有更好的效果。

默认开启线程数量与CPU数量相同,也可以使用-XX:ParalledGCThreads配置线程数量。

server模式下首选

开启ParNew收集器可以使用参数:

  • -XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用Serial Old收集器。(请不要在JDK1.8版本后续使用此参数)
  • -XX:+UseConcMarkSweepGC:新生代使用ParNew收集器,老年代使用CMS收集器。

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

注意:JDK8版本删除了启动ParNew收集器选项。-XX:+UseParNewGC。ParNew收集器将配合CMS收集器使用,因为CMS收集器需要ParNew收集器。

学习来源:https://docs.oracle.com/javase/10/migrate/toc.htm#JSMIG-GUID-9E847B7E-1F6B-4AD4-A5EE-66F8EF8BA9F6

http://openjdk.java.net/jeps/214

3. Parallel Scavenge收集器

    新生代收集器,使用复制算法,多线程收集器(与Serial和ParNew一样需要STW),

可控吞吐量的垃圾收集器。此垃圾收集器无法与CMS配合使用。

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

1)-XX:MaxGCPauseMillis参与值是一个大于0的毫秒数。收集器尽可能的保证内存回收花费时间不超过设定值。

    (JDK11默认值200)

2)-XX:GCTimeRatio参数值是一个大于0且小于100的整数。用来配置垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。(1/1+N)

     例如:-XX:GCTimeRatio=19

                1/(1+19)=0.05。即5%。

    书中所写默认值为99

     (JDK11默认值12)

3)-XX:UseAdaptiveSizePolicy开关参数。默认开启。开关打开后就不需要手工指定新生代的大小(-Xmm)、Eden与Servivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式成为GC自适应的调剂策略。这也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

(JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy)

启动此收集器可使用参数:

  • -XX:+UseParallelGC:新生代使用ParallelGC收集器,老年代使用Serial Old收集器。
  • -XX:+UseParallelOldGC:新生代使用ParallelGC收集器,老年代使用ParallelOldGC收集器。

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

4. Serial Old收集器

    Serial收集器的老年代版本。采用标记整理算法。

主要在client模式下使用。

若要启动Serial Old收集器可以尝试一下参数:

  • -XX:+UseSerialGC:新生代、老年代都使用Serial收集器。
  • -XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用Serial Old收集器。(请不要在JDK8以及后续版本使用此参数,详见ParNew收集器)
  • -XX:+UseParallelGC:新生代使用ParallelGC收集器,老年代使用Serial Old收集器。

如果在server模式下有两大用途。一种是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用。另一种用途就是作为CMS收集器的后备预案,在并发收集发生Conncurrent Mode Failure时使用。

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

5. Paralled Old收集器

    此收集器是Parallel Scavenge收集器的老年代版本。使用标记整理算法。JDK1.6开始提供。

可以使用参数-XX:+UseParallelOldGC开启此收集器。新生代使用Parallel收集器,老年代使用ParallelOldGC收集器。

学习来源:《深入理解Java虚拟机》-- JDK1.7,《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

6. CMS收集器

    老年代收集器。是一种获取最短停顿时间为目标的收集器。通过标记清除算法实现

回收步骤分为:

    1)初始标记(CMS initial mark)--STW

          标记GC Roots能够直接关联到的对象

    2)  并发标记(CMS concurrent mark)

         GC Roots Tracing过程

    3)预清理

         清理前准备以及控制停顿时间

         默认情况下这个预清理步骤是存在的。也可以通过参数-XX:-CMSPrecleaningEnabled来关闭预清理。

    4)重新标记(CMS remark)--STW

          修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录

    5)  并发清除(CMS concurrent sweep)

缺点:

    1)CMS收集器对CPU资源非常敏感,由于CMS的并发标记和并发清除会占用一部分线程资源或CPU资源而导致程序变慢、总吞吐量降低。CMS默认启动的回收线程数是(CPU数量+3/4)。当CPU在4个以上时垃圾收集线程不少于25%的CPU资源,并随着CPU数量的增加而下降。当CPU不足4个时很可能会导致用户程序执行速度忽然降到50%。

    2)CMS收集器无法处理浮动垃圾。由于并发清理阶段用户线程还在运行着,所以会导致会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后。CMS无法在当次收集中处理掉它们,只好留在下一次GC时清理。这一部分垃圾就成为“浮动垃圾”。可以适当调高CMSInitiatingOccupancyFraction的值来提高触发百分比。以便降低内存回收次数。如果CMS运行期间预留内存无法满足程序需要,就会触发一次“Concurrent Mode Failure”失败。这时虚拟机将启动后备预案:临时启动Serial Old收集器来重新进行老年代的垃圾收集工作。

    3)CMS收集器是一款基于标记清除算法实现的收集器,垃圾收集工作结束后会有大量空间碎片产生。很可能会导致老年代还有很大空间,但是无法找到足够大的连续空间来分配当前对象,从而不得不触发一次Full GC。CMS收集器提供了一个+UseCMSCompactAtFullCollection开关参数,用于在CMS收集器顶不住需要机型Full GC的时候开启内存碎片的合并整理过程。内存整理过程无法并发执行。虚拟机还提供了另一个参数-XX:CMSFullGCsBeforeCompaction用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的。

(值得注意的是-XX:+UseCMSCompactAtFullCollection,-XX:+CMSFullGCsBeforeCompaction 这两个参数根据目前了解到,JDK8中还在使用,但是在JDK9中已经删除,如果在JDK9中还在使用则会导致jvm启动失败:“Unrecognized VM option”)

--XX:UseConcMarkSweepGC开关参数。用于开启和关闭CMS收集器

--XX:CMSInitiatingOccupancyFraction是一个0~100之间的整数。用来标记老年代使用比例达到某个比例时触发CMS垃圾收集器。JDK1.5默认值68。表示老年代使用空间达到68%时CMS垃圾收集器会被激活。如果该值设置为-1的话,则由另外两个参数决定启动CMS收集器MinHeapFreeRatio、CMSTriggerRatio。((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0 。

 

学习来源:《深入理解Java虚拟机》-- JDK1.7

学习来源:《实战Java虚拟机:JVM故障诊断与性能优化》--JDK1.8

学习来源:https://www.jianshu.com/p/61bf0e9011c4

GC Roots解释

GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。

GC Root

常说的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。

一个对象可以属于多个root,GC root有几下种:

Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。

Thread - 活着的线程

Stack Local - Java方法的local变量或参数

JNI Local - JNI方法的local变量或参数

JNI Global - 全局JNI引用

Monitor Used - 用于同步的监控对象

Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于"JVM持有"的了。

内容来源:https://www.zhihu.com/question/50381439/answer/226231622

7. G1收集器

    JDK1.7u4开始商用提供。被sun定义为替代JDK1.5中发布的CMS收集器的收集器。他可以管理整个GC堆。采用分区算法。其他收集器的收集范围为整个新生代或者老年代,而G1收集器不同的是,虽然还保留新生代和老年代的概念,它将堆划分为多个大小相等的独立区域(Region),新生代和老年代不再物理隔离,而是作为一部分Region的集合。G1在后台维护者一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。G1中每个对应Region都有一个与之对应的Remembered Set用来避免全堆扫描,当程序发现对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用对象是否处于不同Region之中,如果是,便通过CardTable把相关引用信息记录到被引用对象所属Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

对于G1收集器此处只做简单介绍。更详细的介绍,请参见:https://blog.csdn.net/jl19861101/article/details/88566316

G1收集器的特性:

    并发并行:可以利用多个CPU或CPU核心来缩短STW时间。部分其他收集器原本需要停顿用户线程的GC动作,G1可以通过并发的方式让用户线程继续执行。

    分代收集:采用不同的方式处理新创建的对象和已经存活了一段时间和熬过多次GC的旧对象。

    空间整合:从整体上看与CMS的标记清理算法相同,但从局部(不同的Region之间)来看是基于复制算法实现的。这意味着G1运作期间不会产生内存空间碎片。

    可预测的停顿:降低停顿时间(STW)是G1相对于CMS的一大优势。可以明确指出一个长度为M毫秒的时间片段内。

如果不计算维护Remembered Set操作,G1收集器的运作步骤为:

    1)初始标记(Initial Marking)----标记GC Roots可以直接关联对象,并修改TAMS(Next Top at Mark Start)值,让下一阶段并发标记运行时,能在正确可用的 Region中创建新对象。需要STW。这个阶段会伴随着Minor GC。

    2)Root区域扫描(Root Region Scanning)----由于初始标记阶段必然会伴随一次Minor GC,所以在初始化标记后,eden被清空。并且存货对象被移入survivor区域。在这个阶段,将扫描survivor区域直接可达的老年代区域,并标记这些直接可达的对象,这个阶段是并发执行的。但是不能同Minor GC同时执行。

    3)并发标记(Concurrent Marking)----从GC Roots开始对堆中对象进行可达性分析找出存活对象。

    4)最终标记(Final Marking)----用来修正并发标记期间用户线程持续运行导致的标记变动的那一部分标记记录。虚拟机将这段时间对象变化记录在线程Remembered Set Log里面。最终标记阶段把Remembered Set Log的数据合并到Remembered Set Log中。需要STW。但是并行执行。

    5)筛选回收(Live Data Counting and Evacuation)----对各个Region的回收价值和成本进行排序,根据用户希望的GC停顿时间来定制回收计划。可并发执行。只回收一部分Region。时间是用户可控制的。

参数:

-XX:+-UseG1GC:开关参数。用来开启和关闭G1收集器。

学习来源:《深入理解Java虚拟机》-- JDK1.7

111

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章