JVM垃圾回收(G1、CMS)

在看这段之前,首先要知道JVM的内存结构划分。有些地方要用的,可以自行去百度一下。

生产环境建议开启

-XX:+HeapDumpOnOutOfMemoryError   当堆内存空间溢出时输出堆的内存快照,在java.lang.OutOfMemoryError 异常出现时,输出一个dump.core文件,记录当时的堆内存快照。

-XX:HeapDumpPath=./java_pid<pid>.hprof     用来设置堆内存快照的存储文件路径,默认是java进程启动位置

本地调试:

-XX:+PrintGC

调试跟踪 打印简单的GC信息参数:

 -XX:+PrintGCDetails, +XX:+PrintGCTimeStamps

打印详细的GC信息

-Xlogger:logpath 

设置gc的日志路,如: -Xlogger:log/gc.log, 将gc.log的路径设置到当前目录的log目录下. 

一.垃圾回收的触发

Minor GC

特点: 发生在新生代上,发生的较频繁,执行速度较快

触发条件: Eden区空间不足\空间分配担保

Full GC

特点:主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢

触发条件:

程序内部调用 System.gc()(最好不要,好奇心也不要有啊,)

老年代区域空间不足

空间分配担保失败

JDK 1.7 及以前的永久代(方法区)空间不足,

CMS GC处理浮动垃圾时,如果新生代空间不足,则采用空间分配担保机制,如果老年代空间不足,则触发Full GC

二.垃圾回收判定条件(对象存活的判定)

1.引用计数法(java虚拟机没用)

每个对象创建时分配一个引用计数器,有人引用+1,失效-1(相互引用会出问题)

2.可达性分析(java虚拟机在用)

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

GC Roots 对象:

    a.本地方法栈中局部变量表引用的数据

    b.虚拟机栈中局部变量表的引用数据

    c.元空间中静态属性引用对象

    d.元空间常量的引用

三.垃圾回收算法

1.复制回收

存在空间浪费

将区域划分为相同两块,使用其中一块。这块用完触发回收,将有数据这块存货的数据复制到另一块(实打实的移动,不能用指针),清理掉这块就可以了,这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可。

新生代就是复制回收算法,但是因为新生代对象超过90%以上“朝生夕死”的原因,所以默认划分为 eden:from:to=8:1:1,eden区对象98%都会被回收掉,所以只会浪费10%,而且可以去调节大小(-Xmn)比例(-XX:SurvivorRatio)

2.标记清除

过程:

  1. 首先标记所有需要回收的对象
  2. 统一回收被标记的对象

缺点:

     1.效率问题,标记和清除效率都不高

     2.标记清除之后会产生大量不连续的内存碎片(内存够大的时候就不是碎片了),空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

3.标记清理

首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

 四.垃圾回收器

1.CMS(-XX:+UseConcMarkSweepGC)

一般新生代使用ParNew(复制回收),老年代的用CMS

基于“标记—清除”算法实现

垃圾回收过程

整个过程分为4个步骤,包括:

  1. 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(STW -Stop the world)。
  2. 并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,它在整个回收过程中耗时最长,不需要停顿。
  3. 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(STW)。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
  4. 并发清除:不需要停顿。

借用别人一张图说明一下,

  优点:

    由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

缺点:

    

CPU资源敏感:因为并发阶段多线程占据CPU资源,如果CPU资源不足,效率会明显降低。

浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。

在1.6的版本中老年代空间使用率阈值(92%)

如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。

会产生空间碎片:标记 - 清除算法会导致产生不连续的空间碎片

2.G1(-XX:+UseG1GC)

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。

算法:标记—整理 (old,humongous) 和复制回收算法(survivor)。

1.9之后是默认,1.7之后才有,1.8才算成熟。

内存小的话就不要用了,划分太散了(2048块)

GC模式

Young GC

    选定所有新生代里的Region。通过控制新生代的region个数,即新生代内存大小,来控制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的。

GC过程

初始标记:仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程(STW),但耗时很短。

并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。

最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程(STW),但是可并行执行。

筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

特点

空间整合:不会产生内存碎片

    算法:标记—整理 (humongous) 和复制回收算法(survivor)。

可预测的停顿:

    G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)

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