JVM垃圾收集 知识汇总

总结下自己掌握的关于JVM垃圾回收相关的知识点~

1. 什么样的对象为垃圾?

引用计数法

无法解决循环依赖问题

可达性分析法

从一些roots对象出发,沿着roots对象追踪对象上的引用,能追踪到的对象都是存活的对象;没有被roots对象引用的视为垃圾对象。
可以作为GC Roots的对象有:

  • 虚拟机栈中引用的对象
    常见的就是:Object obj = new Object();
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

2. 垃圾收集算法

标记-清除

标记出所有需要回收的对象,标记完成后回收被标记的对象。
有两个缺点:

  1. 会产生内存碎片,内存碎片太多可能会导致无法分配较大的对象(需要连续的内存空间)
  2. 标记和清除效率不高

复制

内存1分为2,每次只使用其中的1半。当其中的一块空间用完了,就将还存活着的对象复制到另外一块内存空间上,然后把已使用的那一块空间一次清理掉
有2个缺点:

  1. 内存空间浪费,因为每次只使用一半
  2. 存活对象较多时,进行较多的对象复制,效率下降

标记-整理

标记过程仍与“标记-清除”算法一样,但是后续不是直接对可回收对象进行清理。而是让存活的对象都向一端移动,最后清楚掉端边界以外的内存。

3. JVM堆的分代

根据相关实验(?....?), 很多对象存活时间都是很短的, 少部分的对象存活时间会稍长一些,根据这些特性吧,JVM将堆化分为新生代老年代

相关参数配置:

堆的大小: -Xms4G -Xmx4G
新生代和老年代比例:默认1:2,
新生代和survior比例默认: 8:1:1

4. 垃圾回收器的分类

4.1 工作在Young区的垃圾收集器

名称 介绍 采用的算法 STW
Serial 线程收集年轻代 复制算法 STW
ParNew 多线程收集年轻代 复制算法 STW
Parallel Scavenge 吞吐量优先的多线程收集年轻代 复制算法 STW

4.2 工作在Old区的垃圾收集器

名称 介绍 采用的算法 STW
Serial Old 单线程收集Old区 标记-整理 STW
Parallel Old 多线程收集Old区 标记-整理 STW
CMS 并发标记清除收集Old区 标记-清除 和用户线程并行

4.3 YoungGC、MajorGC、MixedGC、FullGC

  • YoungGC:发生在年轻代的GC
  • MajorGC:发生在老年代的GC ,只有CMS
  • MixedGC: 发生在年轻代和老年代的GC, 只有G1
  • FullGC指的是:年轻代+老年代+方法区(元空间/永久代) 整个堆上进行垃圾收集
    产生FullGC的原因
    1. 当Eden区分配满的时候,发现之前younggc的平均晋升对象的大小超过当前Old区的剩余空间时,则不会触发Younggc了而会触发FullGC(除CMS外[它只收集old区],其他能收集old区的垃圾收集器都会收集整个堆)。所以不需要额外触发一次YoungGC,如果触发了YoungGC,可以理解为了减轻fullgc的压力吧。
    2. 当方法区无法分配内存的时候,如Perm Generation、Metaspace满了,会触发FullGC
    3. 调用System.gc()会触发FullGC
    4. 执行jmap -dump:live,format=b,file=heap.bin <pid> 的时候触发FullGC

4.4 CMS-并发标记清除

一款工作在Old区、且可以和用户线程并发执行的基于标记清除算法的老年代收集器,为了缩短用户线程的暂停时间。

1. CMS的工作步骤:
步骤 解释 STW
初始标记 标记那些roots对象 STW
并发标记 从roots对象出发标记存活的对象,用户线程不用暂停 非STW
重新标记 由于步骤2用户线程也在执行,需要重新标记那些引用发生变化的部分 STW
并发收集 多线程并发收集,和用户线程并发执行 非STW
2. CMS的优缺点
  • 优点
    可以让垃圾收集工作和用户线程并发执行,减少系统stw卡顿时间,对响应时间要求高的系统比较何时。
  • 缺点
    1.系统吞吐量降低,因为垃圾收集线程占用了cpu的时间
    2.产生内存碎片

    由于CMS是基于“标记-清除”算法实现的收集器,所以会产生内存碎片。 CMS收集器额外又提供了一个可选择的参数-XX:+UseCMSCompactAtFullCollection开关参数(默认开启),用于CMS收集器在顶不住要进行FullGC的时候开启内存碎片的合并整理过程,内存整理的过程是无法并发执行的,所以会导致停顿时间比长。 虚拟机又提供了一个参数-XX:CMSFullGCsBeforeCompaction用于设置执行多少次不压缩的FULL GC后,跟着来一次带压缩的。默认值是0,表示每次进入FULL GC都进行碎片整理

  1. 产生浮动垃圾

    因为并行收集阶段,用户线程也在执行,有可能新产生垃圾对象,这些对象在本次回收阶段无法被回收,只能等下次了
    所以CMS收集器需要预留足够的空间给用户线程使用,不然会出现Concurrent Mode Failure失败。JDK1.6中,CMS收集器启动的阈值是92%,也就是当老年代使用了92%的空间后就会开启CMS收集,不过我们可以通过参数-XX:CMSInitiatingOccupancyFraction=<n>的值来提高触发百分比,可以降低这个值,给用户线程留多一些空间

  2. FullGC会很慢

    当cms清理不及时的时候,应用线程新产生的对象没有足够的内存可分配时,CMS顶不住了会启动Serial Old进行一次FullGC,这个可就慢了啊! (CMS是不进行FullGC的)

3. CMS的参数

3.1 -XX:+UseConcMarkSweepGC:启用cms
3.2 -XX:ConcGCThreads:并发的GC线程数
3.3 -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理,清理碎片
3.4 -XX:CMSFullGCsBeforeCompaction=0:多少次FullGC之后压缩一次,默认是0,代表每次 FullGC后都会压缩一次
3.5 -XX:CMSInitiatingOccupancyFraction=92: 默认值: 92 当老年代使用达到该比例时会触发CMSGC
3.6 -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(- XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定 值,后续则会自动调整
3.7 -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少 老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在 remark阶段
正式因为上面这些问题,所以没有任意一个jdk版本默认采用CMS收集器,但是它产生的意义是很大的,并发收集!

4.5 G1

4.5.1 G1堆的划分

G1的出现就是来替代CMS的,它能提供更低的延迟。与其他垃圾收集器相比,G1在内存上属于逻辑分代,物理不分代。堆内存不再划分为连续的年轻代和老年代,而是划分为一定数量的堆区域 heap region,这些region可以是Eden,Surviror,Old区的任何一种。所有的Eden区和Surviror区统称为年轻代,所有的Old区称为老年代。 所以是逻辑上分代, 而且某个区域的类型是可变的(当前时刻是属于Eden区,可能等会就是Old区了)。
G1的堆结构

有些点需要注意:

  1. 整个堆被划分为很多个(不超过2048)固定大小的Region,大小可以配置从1M(默认)~32M(-XX:G1HeapRegionSize但是不建议手动改)
  2. 所有的Eden、Survivor在逻辑上统称为年轻代,且不需要连续,这样可以使得Resize Region变得容易
  3. 所有的Old Region在逻辑上统称为老年代,且不需要连续
  4. 还有一种Region类型是:Humongous regions,它用来存放超过Region 50%大小的对象,他们被存储到专门的连续的Region中。而不是把他们存到Old的Region,因为可以节约Old区的空间,降低发生gc的频率。
  5. 一个Region在当时是属于Eden,可能过会儿就成为了Old,也可能是Survior
4.5.2 G1的YoungGC

注意到上图的Young区是不需要连续的, 存活的对象将被转移(复制或者移动)到Survivor区或者Old区(age达到了阈值)。
总结YoungGC的收集过程:

  1. YoungGC只收集Young generation里面的regions
  2. YoungGC是需要STW的,younggc使用多线程完成。
  3. Eden区和Surviror区的大小会被计算用于下一次YoungGC,用于优化给定的gc暂停时间,这种方式使得resize region变得容易,可以在需要的时候把他们变大,或者变小。

YoungGC并不是说现有的Eden区放满了就会马上触发,而且G1会计算下现在Eden区回收大 概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代 的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时 间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC

4.5.3 G1的MixedGC

在old区回收会经历下面几个阶段,一些阶段也会在Young区发生。

阶段 描述
(1) Initial Mark
(Stop the World Event)
会STW. 依托于一次普通的YoungGC. 在Old区标记出roots对象.
逻辑上global concurrent marking与evacuation是相对独立的。但是global concurrent marking之中initial marking要做的事情正好跟young GC要做的事情有重叠——遍历根集合,所以在实现上把它们安排在一起做。
一个young GC可以顺带做initial marking,也可以不做;
一个正常的initial marking(除非是System.gc()+ -XX:+ExplicitGCInvokesConcurrent)总是搭在young GC上做。
(2) Root Region Scanning Scan survivor regions for references into the old generation. This happens while the application continues to run. The phase must be completed before a young GC can occur.
(3) Concurrent Marking 在整个堆上查找存活的对象,计算存活率较低的regions. 此时用户线程可以运行. 这个阶段可以被年轻代垃圾收集中断.
(4) Remark
(Stop the World Event)
重新标记存活的对象. 用一种叫做 snapshot-at-the-beginning (SATB) 的算法(比CMS的算法快).
(5) Cleanup
(Stop the World Event and Concurrent)
1. 对存活的对象和空regions做计算. (Stop the world)
2. Scrubs the Remembered Sets. (Stop the world)
3. 回收空的Regions. (Concurrent)
(*) Copying
(Stop the World Event)
移动或者复制存活的对象到未使用的regions里面,此阶段是STW的.
可以在年轻代完成(在log中标记为:[GC pause (young)]).
也可以在年轻代和老年代完成(在log中标记为: [GC Pause (mixed)]).

MixedGC: 收集young gen里的所有region,外加若干选定的old gen region和大对象Region。控制mixed GC开销的手段是选多少个、哪几个old gen region, 根据用户设置的期望暂停时间,来选择那些收益较高的old regions。 所以:MixedGC不是FullGC。

4.5.4 G1的FullGC

如果MixedGC的速度跟不上程序程序分配新对象的速度,导致Old区被填满
会切换到G1之外的Serial Old单线程GC来收集整个堆FullGC.

4.5.5 G1最佳实践配置
  1. 开启G1

    java -Xmx4G -Xms4G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45

  2. 完整的G1的配置:
Option and Default Value Description
-XX:+UseG1GC Use the Garbage First (G1) Collector
-XX:MaxGCPauseMillis=n Sets a target for the maximum GC pause time. This is a soft goal, and the JVM will make its best effort to achieve it.
-XX:InitiatingHeapOccupancyPercent=n Percentage of the (entire) heap occupancy to start a concurrent GC cycle. It is used by GCs that trigger a concurrent GC cycle based on the occupancy of the entire heap, not just one of the generations (e.g., G1). A value of 0 denotes 'do constant GC cycles'. The default value is 45.
-XX:NewRatio=n Ratio of new/old generation sizes. The default value is 2.
-XX:SurvivorRatio=n Ratio of eden/survivor space size. The default value is 8.
-XX:MaxTenuringThreshold=n Maximum value for tenuring threshold. The default value is 15.
-XX:ParallelGCThreads=n Sets the number of threads used during parallel phases of the garbage collectors. The default value varies with the platform on which the JVM is running.
-XX:ConcGCThreads=n Number of threads concurrent garbage collectors will use. The default value varies with the platform on which the JVM is running.
-XX:G1ReservePercent=n Sets the amount of heap that is reserved as a false ceiling to reduce the possibility of promotion failure. The default value is 10.
-XX:G1HeapRegionSize=n With G1 the Java heap is subdivided into uniformly sized regions. This sets the size of the individual sub-divisions. The default value of this parameter is determined ergonomically based upon heap size. The minimum value is 1Mb and the maximum value is 32Mb.

5. 记录GC的日志

-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=10m
-Xloggc:/xxx/logs/gc.log

持续更新~~

参考文献:

Major GC和Full GC的区别是什么?
Oracle G1
R大神关于G1的原理介绍
美团-G1介绍

原文地址:https://www.cnblogs.com/yangweiqiang/p/13525862.html