java中的垃圾回收机制简介

内存空间是有限的,运行时若是不能获取到内存,会抛出OutOfMemory,一种有效的解决措施是,抛弃那些程序永远不会再也不用到的对象,腾出空间。html

如何定义对象不会用到

  1. 给对象添加一个引用计数器,每当这个对象被引用一次就加1,每当这个对象的引用失效1次,就减1,那么引用次数为0的就没有再用了,非0就表明还有用,可是引用计数器很难解决循环引用java

    新建对象的引用计数为1,若是两个新建对象互相引用,那么他们的引用计数为2,此时若是只将原新对象置为null,只会各自使得引用计数减1,这种场景下获得的结果引用结果是1,于是仅靠这种粗略的检查并不能达到一个好的效果算法

  2. 给对象的引用作追踪。能够定义一组集合,认定从这个集合出发,可以追溯到的全部对象,都是可用的,其他的都是不可用的安全

    这种集合也称做 GC Roots,它定义一组根引用,包括当前全部正在被调用的方法的引用类型参数、局部变量、临时值;方法区中的常量引用对象;本地方法栈中的JNI等等bash

可引用对象的细分

干掉没有引用的对象,没什么问题,可是若是内存空间仍然不够,能够干掉部分虽然可用,可是不那么重要的对象来“确保大局”,java对此细分了强引用、软引用、弱引用、虚引用多线程

详见reference 引用并发

经常使用的垃圾回收算法思想

  1. 标记-清除。首先标记出须要清除的对象,而后再统一清除。
  2. 复制算法。将一块内存分为两半,每次只用一半,当对使用的这块内存回收时,将能够用的对象复制到另外一块内存上,而后一次性清除全部使用过的内存空间
  3. 标记-整理。思想与标记-清除一致,只是在清除以前,会先把全部存活的对象都移向一端,而后清掉端边界外的内存
  4. 分代思想。根据对象的存活周期将内存划分红几块,对不一样的存活时间使用不一样的垃圾回收算法

分代GC带来的好处

  1. 大多数状况下,数据都会知足这么一个假设:大部分对象的存活时间很短,而其它的对象则有可能存活时间很长。在这一假设前提下划分为年轻代和老年代。配置年轻代占据堆中较小的一块,新建立的对象都在年轻代里面,GC时,因为大部分对象都会消亡,只会留下较小的部分,这样适合使用复制算法的思想来处理,这样的划分便下降了单次GC的时间长度(遍历的空间少),同时提升了GC的效率(回收的多)。hotspot中年轻代被划分红8:1:1,这里其实就是认为90%的对象都会被回收,10%用来保留活下来的,这也就意味着复制算法每次只有10%的空间浪费
  2. 为了更快的释放空间,一边能处理应用内存的分配。 并发GC的本质是GC一边搜集,应用一边产生,若是GC的速度跟不上产生的速度,那么垃圾就会组件堆积,最终应用分配请求只能停下来等GC追赶,所以越快释放出越多的空间,就能越好应付越高的应用分配内存的速率,从而让GC以完美的并发模式工做。

GC为何要分代eclipse

何时能够回收

要作回收,首先得知道哪些对象是可达的(存活的),而要知道可达性,对于对象引用追踪这种思想,就得要去遍历整个GC根集合。而要作到精准的枚举,就须要知道哪些栈的槽位有引用,哪些寄存器有引用,于是须要有一些位置去保存这些信息,而可以保存这些信息的地方即 安全点或者安全区域jsp

可以保存这些信息的地方一定也是知道引用状况的地方,这些地方也就能够执行GCpost

hotspot中的垃圾收集器

不管使用哪一种收集器,在收集开始的时候都是从 safepoint开始

serial年轻代收集器

"古老"的收集器,使用单线程收集,它工做时必须暂停全部用户的线程,直到收集结束。对于年轻代的收集则使用复制算法。 能够用于Client模式下的虚拟机

ParNew年轻代收集器

serial的多线程版本。多线程收集,它工做时必须暂停全部用户的线程,直到收集结束。对于年轻代的收集则使用复制算法。与CMS收集器配合工做,使用 -XX:UseConcMarkSweepGC的默认年轻代收集器

Parallel Scavenge年轻代收集器

多线程收集器。它的目标是提供一个可控的吞吐量:

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

与缩短停顿时间的收集器相比,它的目标是高效率的利用CPU的时间,尽快完成运算任务。另外它还支持自适应调节:好比年轻代大小、Eden和Survior的比例、晋升老年代的大小等,来达到最佳的吞吐量。适合后台运算而不须要太多交互的任务,不能配合CMS工做

缩短停顿时间的关注点则是在于提供良好的响应速度,从而提高用户体验

Serial老年代收集器

单线程收集。它须要暂停全部用户线程,直到收集结束。年老代使用标记-整理算法。它一样适用于Client模式下的虚拟机

若是是Server模式,在JDK1.5以及以前能够用来配合Parallel Scavenge搭配使用,以及做为CMS收集器的预备方案,在发生Concurrent Mode Failure时使用

Parallel 老年代收集器

多线程收集。它须要暂定全部用户线程,直到收集结束。使用标记整理算法,它也是以吞吐量优先,在JDK1.6中提供,用来配合Parallel Scavenge使用

CMS(Concurrent Mark Sweep) 老年代收集器

并发收集。它分为4个阶段:

  • 初始标记:须要暂停用户线程。用于标记GC Roots能直接关联到的对象
  • 并发标记:不需暂停用户线程。用于标记全部活着的对象
  • 从新标记:须要暂停用户线程。修正由于用户线程运行而致使的标记变更的对象
  • 并发清除:不需暂停用户线程。清除消亡的对象

它总体使用的是标记-清除 算法,系统停顿时间短,适用响应速度快,用户体验好
缺点:

  1. 并发意味着它会占用CPU资源,吞吐量就低;
  2. 因为清理的时候用户线程还在运行,那么用户线程也是须要空间的,若是空间不够,就产生Concurrent Mode Failure,转而使用Serial Old,另外用户运行也会不断的产生垃圾,这部分没法清除(浮动垃圾),

    所以有设置CMS触发的参数 -XX:CMSInitiatingOccupancyFraction

  3. 标记-清除以后会产生碎片

    能够经过 -XX:CMSCompactAtFullCollection设置是否要清理碎片,以及 -XX:CMSFullGCsBeforeCompaction来表示多这次运行不压缩的Full GC后来一次压缩

G1(Garbage-First)收集器

新生代和老年代均可以收集。大体步骤以下:

  1. 初始标记:须要暂停用户线程。用于标记GC Roots能直接关联到的对象,以及实现G1算法相关的操做
  2. 并发标记:不准须要暂停用户线程。用于标记活着的对象
  3. 最终标记:须要停顿并行执行。用于修正因用户运行而致使的标记变动
  4. 筛选回收:不须要暂停,并发执行。根据G1算法的细节进行回收价值和成本的排序,生成执行计划

与CMS相比优势:

  • 空间整合:G1从总体上来说是基于标记-整理算法,从局部来说是基于“复制”算法实现,这意味着运行期间不会产生内存空间碎片
  • 可预测的停顿:使用者能够指定在长度为M毫秒的时间片断内,消耗时间不超过N毫秒

JDK 7 引入

ZGC

并发垃圾收集器。几乎全部的阶段都是并发执行

ZGC仍然会压缩堆,压缩堆这件事,一般意味着

  • 将或者的对象移到堆的一端
  • 执行移动过程当中须要暂停应用线程

压缩主要会遇到这么些问题

  • 在搬运对象到另外一个内存地址的时候,另外一个线程也同时会对对象进行读和写
  • 搬运成功后,其它有这个对象引用的也必须去跟新他们的引用地址

ZGC过程地址

JDK 11引入

垃圾收集器参数

  • UseParNewGC:使用ParNew+Serial Old收集器
  • UseConcMarkSweepGC:使用ParNew+CMS+Serial Old收集器
  • UseParallelGC:使用Parallel Scavenge+Serial Old
  • UseParallelOldGC:使用Parallel Scavenge+Parallel Old

运行调节的参数

  • SurvivorRatio:Eden与Survivor的分配比例
  • Newratio:年轻代和年老代的比值,好比4表示young:old=1:4
  • PretenureSizeThreshold:直接晋升到老年代的对象大小
  • MaxTenuringThreshold:晋升到老年代的年龄
  • ParallelGCThreads:并行GC内存回收的线程数
  • CMSCompactAtFullCollection:运行CMS后是否须要碎片整理
  • CMSFullGCsBeforeCompaction:运行CMS若干次后再进行碎片整理
  • CMSInitiatingOccupancyFraction:使用CMS,老年代空间使用多少后触发GC,默认68%

专业名词

名字讲解地址

Partial GC:不收集整个GC堆

  • young GC:只收集年轻代
  • Old GC:只收集年老代,限CMS的并行收集
  • Mixed GC:收集年轻代和年老代,限G1

Full GC:收集整个堆,包括年轻代,年老代,永久带(若是有的话)

Minor GC通常指的是young GC;Major GC一般和Full GC等价,另外因为名词混用,也可能指的是Old GC

触发young gc的时候,若是发现以前young GC的平均大小比目前老年代的剩余空间大,则触发Full GC,永久带若是没有足够的空间,也会触发Full GC

注意: ParallelScavenge 则是在每次触发Full GC以前会先执行一次young gc,再执行full gc;

GC 文件

使用 jstat -gc pid time_interval count格式可以查看Java堆情况

  • gcutil能够用来查询百分比
  • gcnew/gcold分别查看年轻代和年老代的GC

结果以下

S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
16960.0 16960.0 5116.0  0.0   136064.0 93854.9   339724.0   271888.9  152936.0 149578.7 20444.0 19683.3    220    2.122  19      0.923    3.045
复制代码
  • S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
  • EC、EU:Eden区容量和使用量
  • OC、OU:年老代容量和使用量
  • PC、PU:永久代容量和使用量
  • YGC、YGT:年轻代GC次数和GC耗时
  • FGC、FGCT:Full GC次数和Full GC耗时
  • GCT:GC总耗时

使用 -XX:+PrintGCDetails 能够显示GC的状况,形如

[GC[ParNew: 6996K->1202K(78656K), 0.0036460 secs][CMS: 0K->1163K(174784K), 0.0311840 secs] 6996K->1163K(253440K), [CMS Perm : 3060K->3059K(21248K)], 0.0349020 secs] [Times: user=0.03 sys=0.02, real=0.03 secs] 
复制代码
  • [GC 代表停顿的类型,有Full则代表发生了Full GC
  • [ParNew 表示垃圾收集的区域与对应的GC收集器 ;其中 [ParNew 代表用的是ParNew收集器; [DefNew 代表使用的是Serial收集器;[PSYoungGen 代表使用的是 Parallel Scavenge收集器;[CMS 这种表示CMS收集器
  • 6996K->1202K(78656K) 表示“GC前该区域已使用容量->GC后该区域已使用容量(该内存区域的总容量)”
  • 0.0036460 secs 表示该内存区域GC所用的时间
  • 6996K->1163K(253440K) (方括号外)的表示"GC前java堆已使用的容量->GC后Java堆使用的容量(Java堆总容量)"
  • [Times: user=0.03 sys=0.02, real=0.03 secs] 分别表示用户态消耗的CPU时间、内核态的CPU时间和操做从开始到结束所通过的强钟时间

    墙钟时间包含各类非运算的等待耗时,例如等待磁盘IO,CPU时间则不包含这些,可是多线程会叠加CPU的时间

相关文章
相关标签/搜索