JVM 垃圾回收

三个问题:

  • 要回收什么内存?
  • 什么时候回收?
  • 怎么回收?

要回收什么内存?

答:

要回收已死对象的内存。那怎么判断对象是否死亡呢?通过算法:

引用计数法

给每个创建的对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1,
引用失效时-1,所以当计数值为0时表示对象已经不能被使用。
引用计数算法大多数情况下是个比较不错的算法,简单直接,也有一些著名的应用案例。
但是对于Java虚拟机来说,并不是一个好的选择,因为它很难解决对象直接相互循环引用的问题。

优点:
实现简单,执行效率高,很好的和程序交织。
缺点:
如遇到循环应用的情况无法释放资源

可达性分析算法

此算法的基本思路就是通过一系列的“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。
搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”,即该对象是不可用的。

在这里插入图片描述
可作为GC Root的对象有:

  • 栈帧中局部变量表中的reference引用所引用的对象
  • 方法区中static静态引用的对象
  • 方法区中final常量引用的对象
  • 本地方法栈中引用的对象
  • jvm初始化内部的引用,例如:基本数据类型对应的class对象;常驻的异常对象(NullPointExcepiton、OutOfMemoryError);系统类加载器等
  • 被同步锁synchronized{}持有的对象
  • 反映Java虚拟机内部情况的JMXBean、 JVMTI中注册的回调、 本地代码缓存等。

优点:
缺点:

判断对象是否存活(什么时候回收?)

即使在可达性分析里边判定为不可达的对象,也不是非死不可的,它们至少需要经历两次标记过程

第一次标记:

  1. 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链, 那它将会被第一次标记。
  2. 随后进行一次筛选, 筛选的条件是此对象是否有必要执行finalize()方法。
    没有必要:
    假如对象没有覆盖finalize()方法, 或者finalize()方法已经被虚拟机调用过, 那么虚拟机将这两种情况都视为“没有必要执行”。这时候对象会被直接回收
    有必要:
    如果这个对象被判定为确有必要执行finalize()方法, 那么该对象将会被放置在一个名为F-Queue的 队列之中, 并在稍后由一条由虚拟机自动建立的、 低调度优先级的Finalizer线程去执行它们的finalize() 方法。
    finalize()方法是对象逃脱死亡命运的最后一次机会, 稍后收集器将对F-Queue中的对象进行第二次小规模的标记, 如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可, 譬如把自己 (this关键字) 赋值给某个类变量或者对象的成员变量, 那在第二次标记时它将被移出“即将回收”的集 合; 如果对象这时候还没有逃脱, 那基本上它就真的要被回收了。

在这里插入图片描述
注意!!!:
Finalizer线程去执行它们的finalize() 方法, 这里所说的“执行”是指虚拟机会触发这个方法开始运行, 但并不承诺一定会等待它运行结束。 这样做的原因是, 如果某个对象的finalize()方法执行缓慢, 或者更极端地发生了死循环, 将很可能导 致F-Queue队列中的其他对象永久处于等待, 甚至导致整个内存回收子系统的崩溃。(finalize() 方法的错误并不能中断程序,也不能打印出日志)

四种引用

强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。

软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

弱引用(WeakReference)
用来描述那些非必须对象, 但是它的强度比软引用更弱一些, 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。 当垃圾收集器开始工作, 无论当前内存是否足够, 都会回收掉只 被弱引用关联的对象。 在JDK 1.2版之后提供了WeakReference类来实现弱引用。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用(PhantomReference)
①虚引用必须和引用队列 (ReferenceQueue)联合使用。
②当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

怎么回收?(垃圾收集算法)

分代收集理论

分代收集就是根据对象的生命周期进行划分,然后进行分区处理。当前商业虚拟机的垃圾收集器, 大多数都遵循了“分代收集”(Generational Collection)的理论进行设计, 分代收集名为理论, 实质是一套符合大多数程序运行实际情况的经验法则, 它建立在两个分代假说之上:

1) 弱分代假说(Weak Generational Hypothesis) : 绝大多数对象都是朝生夕灭的。
2) 强分代假说(Strong Generational Hypothesis) : 熬过越多次垃圾收集过程的对象就越难以消亡。

在Java堆划分出不同的区域之后, 垃圾收集器才可以每次只回收其中某一个或者某些部分的区域 ——因而才有了“Minor GC”“Major GC”“Full GC”这样的回收类型的划分; 也才能够针对不同的区域安 排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算 法”“标记-整理算法”等针对性的垃圾收集算法。

他针对不同分代的类似名词, 为避免产生混淆, 在这里统一定义 :

部分收集(Partial GC) : 指目标不是完整收集整个Java堆的垃圾收集, 其中又分为:

  • 新生代收集(Minor GC/Young GC): 指目标只是新生代的垃圾收集。
  • 老年代收集(Major GC/Old GC): 指目标只是老年代的垃圾收集,目前只有CMS收集器会有单 独收集老年代的行为。
  • 混合收集(Mixed GC): 指目标是收集整个新生代以及部分老年代的垃圾收集。 目前只有G1收集器会有这种行为。

整堆收集(Full GC) : 收集整个Java堆和方法区的垃圾收集

标记-删除算法

算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象, 在标记完成后,统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收所有未被标记的对象。

缺点:

  • 第一个是执行效率不稳定, 如果Java堆中包含大量对 象, 而且其中大部分是需要被回收的, 这时必须进行大量标记和清除的动作, 导致标记和清除两个过 程的执行效率都随对象数量增长而降低;
  • 第二个是内存空间的碎片化问题, 标记、 清除之后会产生大 量不连续的内存碎片, 空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。

在这里插入图片描述

标记-复制算法

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题, 1969年Fenichel提出了一种称为“半区复制”(Semispace Copying) 的垃圾收集算法, 它将可用 内存按容量划分为大小相等的两块, 每次只使用其中的一块。 当这一块的内存用完了, 就将还存活着 的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。

缺点:

  • 需要提前预留一半的内存区域用来存放存活的对象(经过垃圾收集后还存活的对象),这样导致可用的对象区域减小一半,总体的GC更加频繁了
  • 如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
  • 如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。
    标记-复制算法在对象存活率较高时就要进行较多的复制操作, 效率将会降低。 更关键的是, 如果不想浪费50%的空间, 就需要有额外的空间进行分配担保, 以应对被使用的内存中所有对象都100%存活的极端情况, 所以在老年代一般不能直接选用这种算法。

在这里插入图片描述
注意:
现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代, IBM公司曾有一项专门研 究对新生代“朝生夕灭”的特点做了更量化的诠释——新生代中的对象有98%熬不过第一轮收集。 因此 并不需要按照1∶1的比例来划分新生代的内存空间。Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的 Survivor空间, 每次分配内存只使用Eden和其中一块Survivor。 发生垃圾搜集时, 将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上, 然后直接清理掉Eden和已用过的那块Survivor空 间。 HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1, 也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%) , 只有一个Survivor空间, 即10%的新生代是会被“浪费”的。

标记-整理算法

针对老年代对象的存亡特征, 1974年Edward Lueders提出了另外一种有针对性的“标记-整 理”(Mark-Compact) 算法, 其中的标记过程仍然与“标记-清除算法”一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存

优缺点

标记-清除算法标记-整理算法的本质差异在于前者是一种非移动式的回收算法, 而后者是移动式的。 是否移动回收后的存活对象是一项优缺点并存的风险决策:

是否移动对象都存在弊端, 移动则内存回收时会更复杂, 不移动则内存分配时会更复杂。 从垃圾收集的停顿时间来看, 不移动对象停顿时间会更短, 甚至可以不需要停顿, 但是从整个程序的吞吐量来看, 移动对象会更划算。

在这里插入图片描述

垃圾收集器

在这里插入图片描述

Serial收集器

单线程收集器,“单线程”的意义不仅仅说明它只会使用一个CPU或一个收集线程去完成垃圾收集工作;更重要的是它在垃圾收集的时候,必须暂停其他工作线程,直到垃圾收集完毕;
在这里插入图片描述
缺点:
这项工作是由虚拟机在后台自动发起和自动完成的, 在用户不可知、 不可控的情况 下把用户的正常工作的线程全部停掉, 这对很多应用来说都是不能接受的。
优点:
Serial收集器由于简单并且高效;
对于单CPU环境来说,由于Serial收集器没有线程间的交互,专心做垃圾收集自然可以做获得最高的垃圾收集效率

使用方式:-XX:+UseSerialGC

ParNew收集器

ParNew收集器实质上是Serial收集器的多线程并行版本, 除了同时使用多条线程进行垃圾收集之外, 其余的行为包括Serial收集器可用的所有控制参数 、 收集算法、 Stop The World、 对象分配规则、 回收策略等都与Serial收集器完全一致, 在实现上这两种收集器也共用了相当多的代码

在这里插入图片描述
ParNew收集器在单CPU服务器上的垃圾收集效率绝对不会比Serial收集器高;
但是在多CPU服务器上,效果会明显比Serial好

使用方式: -XX+UseParNewGC
设置线程数: -XX:ParllGCThreads

Parallel Scavenge收集器

又称为吞吐量优先收集器,和ParNew收集器类似,是一个新生代收集器。使用复制算法的并行多线程收集器。
Parallel Scavenge是Java1.8默认的收集器,特点是并行的多线程回收,以吞吐量优先。

特点:

  • Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput);
    吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
    (代码运行时间99分钟,垃圾收集时间为1分钟,那么吞吐量就是99%,虚拟机总运行时间100分钟)
  • 自适应调节策略,自动指定年轻代、Eden、Suvisor区的比例。

适用场景:

适合后台运算,交互不多的任务,如批量处理,订单处理,科学计算等。

使用方式:

  • -XX:+UseParallelGC

  • -XX:MaxGCPauseMillis 最大垃圾收集停顿时间

-XX: MaxGCPauseMillis参数允许的值是一个大于0的毫秒数, 收集器将尽力保证内存回收花费的时间不超过用户设定值。 不过大家不要异想天开地认为如果把这个参数的值设置得更小一点就能使得系统的垃圾收集速度变得更快, 垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的:
系统把新生代调得小一些, 收集300MB新生代肯定比收集500MB快, 但这也直接导致垃圾收集发生得更频繁, 原来10秒收集一次、 每次停顿100毫秒, 现在变成5秒收集一次、 每次停顿70毫秒。 停顿时间的确在下降, 但吞吐量也降下来了。

  • -XX:GCTimeRatio 吞吐量大小

-XX: GCTimeRatio参数的值则应当是一个大于0小于100的整数, 也就是垃圾收集时间占总时间的比率, 相当于吞吐量的倒数。 假设GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。譬如把此参数设置为19, 那允许的最大垃圾收集时间就占总时间的5%(即1/(1+19)) , 默认值为99, 即允许最大1%(即1/(1+99)) 的垃圾收集时间

  • -XX:ParllGCThreads 设置年轻代线程数

当cpu核数小于等于8,默认与cpu核数相同; 当cpu核数超过8, ParllGCThreads设置为 3+(5*CPU_COUNT)/8 (**TPS:**8核以下,有几核就是几个线程,8核以上用公式计算)

  • -XX:+UseAdaptiveSizePolicy

有了这个参数之后,就不要手工指定年轻代、Eden、Suvisor区的比例,晋升老年代的对象年龄等,因为虚拟机会根据系统运行情况进行自适应调节

Serial Old收集器

Serial Old是Serial收集器的老年代版本, 它同样是一个单线程收集器, 使用标记-整理算法。 这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。

特点:

  • 针对老年代
  • 采用“标记-整理算法”
  • 单线程收集

在这里插入图片描述

应用场景:

1)在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);
2)作为 CMS收集器的后备预案 ,在并发收集发生Concurrent Mode Failure时使用

使用方式: -XX:+UseSerialGC

注意事项:
需要说明一下, Parallel Scavenge收集器架构中本身有PS MarkSweep收集器来进行老年代收集, 并非 直接调用Serial Old收集器, 但是这个PS MarkSweep收集器与Serial Old的实现几乎是一样的, 所以在官方的许多资料中都是直接以Serial Old代替PS MarkSweep进行讲解

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本, 支持多线程并发收集, 基于标记-整理算法实现。 这个收集器是直到JDK 6时才开始提供的, 在此之前, 新生代的Parallel Scavenge收集器一直处于相 当尴尬的状态, 原因是如果新生代选择了Parallel Scavenge收集器, 老年代除了Serial Old(PS MarkSweep) 收集器以外别无选择, 其他表现良好的老年代收集器, 如CMS无法与它配合工作。

应用场景
JDK1.6及之后用来代替老年代的Serial Old收集器;
(特别是在Server模式,多CPU的情况下;这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合;)

使用方式: -XX:+UseParallelOldGC

CMS收集器

CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器,CMS收集器的关注点尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越短就越适合与用户交互的程序,目前很大一部分的java应用几种在互联网的B/S系统服务器上,这类应用尤其注重服务器的响应速度,系统停顿时间最短,给用户带来良好的体验,CMS收集器使用的算法是标记-清除算法

4个步骤

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

其中初始标记、并发标记都需要stopTheWorld

在这里插入图片描述

  • 初始标记(Initial-Mark):这个阶段程序所有的工作线程都将会因为"Stop-the-Wold"机制而出现短暂的的暂停,这个阶段的主要任务标记处GC Roots 能够关联到的对象.一旦标记完成后就恢复之前被暂停的的所有应用。 由于直接关联对象比较小,所以这里的操作速度非常快。
  • 并发标记(Concurrent-Mark):从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要暂停用户线程, 用户线程可以与垃圾回收器一起运行。
  • 重新标记(Remark):由于并发标记阶段,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此,为了修正并发标记期间因为用户继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常比初始标记阶段长一些,但也远比并发标记阶段时间短。
  • 清除并发(Concurrent-Sweep):此阶段清理删除掉标记判断已经死亡的对象,并释放内存空间。由于不需要移动存活对象,所以这个阶段可以与用户线程同时并发运行。

由于最消耗事件的并发标记与并发清除阶段都不需要暂停工作,所以整个回收阶段是低停顿(低延迟)的。

可达性分析:

垃圾回收器的工作流程大概如下:
1、标记出哪些对象是存活的,哪些对象需要回收。
2、进行回收(删除、复制、整理),如果对象移动过(例如:复制、整理),则需要更新引用

三色标记

三色标记(Tri-color Marking)作为工具来辅助推导, 把遍历对象图过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:

  • 白色:尚未访问过。
  • 黑色: 本对象已经访问过,而且本对象引用的其他对象已经访问过了。
  • 灰色:是一个中间态)表示 本对象已经访问过,但本对象引用的其他对象尚未完全访问完,全部访问后,会转为黑色。

在这里插入图片描述
访问过程:

  1. 初始时,对象都在白色集合中
  2. 将GC Root直接引用的对象放到灰色集合中
  3. 从灰色集合中获取对象
    3.1. 将本对象引用到的其他对象全部挪到灰色集合中去;
    3.2. 将本对象挪到黑色集合中去
  4. 重复步骤3,直到灰色集合为空结束
  5. 结束后,仍在白色集合中的对象为GC Root 不可达对象,可以被回收

Stop The World指的是用户程序暂停,以下简称 STW)时,对象间的引用 是不会发生变化的,可以轻松完成标记。 而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。

多标情况:
在这里插入图片描述
此刻之后,对象E/F/G是“应该”被回收的。然而因为E已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存
这部分本应该回收 但是 没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性,只是需要等到下一轮垃圾回收中才被清除。

漏标情况

在这里插入图片描述
假设GC线程已经遍历到E(变为灰色了),此时应用线程先执行了:

var G = objE.fieldG;
objE.fieldG = null; // 灰色E 断开引用 白色G
objD.fieldG = G; // 黑色D 引用 白色G

此时切回GC线程继续跑,因为E已经没有对G的引用了,所以不会将G放到灰色集合;尽管因为D重新引用了G,但因为D已经是黑色了,不会再重新做遍历处理。 最终导致的结果是:G会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的,但这种情况发生的概率极小,需要满足的条件比较 苛刻。

CMS缺点:

  1. 对CPU资源非常敏感
    其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
    CMS默认启动的回收线程数是**(处理器核心数量 +3) /4**,也就是说, 如果处理器核心数在四个或以上, 并发回收时垃圾收集线程只占用不超过25%的CPU资源。核心数越少,占用的CPU资源比例越高。

  2. 无法处理浮动垃圾
    由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。

  3. 使用标记-删除算法,会产生空间碎片。
    当空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

G1收集器

Garbage First是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。

G1收集器的特点:

  1. G1把内存划分为多个独立区域的Region。
  2. G1仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
  3. G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW
  4. G1整体整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
  5. G1的停顿可预测,能够明确指定在一个时间段内,消耗在垃圾收集上的时间不超过设置时间
  6. G1跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾

Region区域:

G1不再坚持固定大小以及固定数量的 分代区域划分, 而是把连续的Java堆划分为多个独立区域(Region) , 每一个Region都可以 根据需要, 扮演新生代的Eden空间、 Survivor空间, 或者老年代空间。(新生代,老年代都是由多个Region组成)

在这里插入图片描述
将整个堆空间划分为若干个小的区域

  • 使用G1收集器时,它将Java堆划分成约为2048个大小相同的独立Region模块,每个Region块可以根据空间的实际大小而定,为2的N次幂,即1M,2M,4M,8M,32M。
  • 虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。
  • G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。主要用于存储大对象,如果超过1 .5个region,就放到H。一般被视为老年代.

G1 GC过程

G1提供了两种GC模式,Young GC和Mixed GC,两种均是完全Stop The World的。

  • Young GC:选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
    在这里插入图片描述
    在这里插入图片描述

  • Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking全区并发标记)统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。所以MIXGC回收的内存区域是新生代+老年代。

注意!!!:YGC与MIXGC都是采用多线程复制清除,整个过程会STW。 G1的低延迟原理在于其回收的区域变得精确并且范围变小了。

全区并发标记(global concurrent marking)

  • 初始标记
    Initial Mark初始标记是一个STW事件,其完成工作是标记GC ROOTS 直接可达的对象。并将它们的字段压入扫描栈(marking stack)中等待后续扫描。
    因为 STW,所以通常YGC的时候借用YGC的STW顺便启动Initial Mark,也就是启动全局并发标记,全局并发标记YGC在逻辑上独立。
  • 根区域扫描 Root Region Scanning
    根区域扫描是从Survior区的对象出发,标记被引用到老年代中的对象,并把它们的字段在压入扫描栈(marking stack)中等到后续扫描。
    与Initial Mark不一样的是,Root Region Scanning不需要STW与应用程序是并发运行。Root Region Scanning必须在YGC开始前完成。
  • 并发标记
    不需要STW。不断从扫描栈取出引用递归扫描整个堆里的对象。每扫描到一个对象就会对其标记,并将其字段压入扫描栈。重复扫描过程直到扫描栈清空。过程中还会扫描SATB write barrier所记录下的引用。
  • 最终标记
    STW操作。在完成并发标记后,每个Java线程还会有一些剩下的SATB write barrier记录的引用尚未处理。这个阶段就负责把剩下的引用处理完。
  • 筛选回收
    STW操作,清点出有存活对象的Region和没有存活对象的Region(Empty Region)
    STW操作,更新Rset
    Concurrent操作,把Empty Region收集起来到可分配Region队列。

在这里插入图片描述
经过global concurrent marking,collector就知道哪些Region有存活的对象。并将那些完全可回收的Region(没有存活对象)收集起来加入到可分配Region队列,实现对该部分内存的回收。
对于有存活对象的Region,G1会根据统计模型找出收益最高、开销不超过用户指定的上限的若干Region进行对象回收。这些选中被回收的Region组成的集合就叫做collection set 简称Cset

在YGC中的Cset是选定所有young gen里的region。通过控制young gen的region个数来控制young GC的开销。

在MIXGC中的Cset是选定所有young gen里的region,外加根据global concurrent marking统计得出收集收益高的若干old gen region

STAB

STAB全称为snapshot-at-the-beginning,其目的是为了维持并发GC的正确性
(如果标记过程是STW的话,那GC的正确性是一定能保证的。但如果一边标记,一边应用在变更堆里面对象的引用,那么标记的正确性就不一定能保证了。)

STAB的做法是在GC开始时对内存进行一个对象图的逻辑快照(snapshot),通过GC Roots tracing 参照并发标记的过程,只要被快照到对象是活的那在整个GC的过程中对象就被认定的是活的,即使该对象的引用稍后被修改或者删除。同时新分配的对象也会被认为是活的,除此之外其它不可达的对象就被认为是死掉了。这样STAB就保证了真正存活的对象不会被GC误回收,但同时也造成了某些可以被回收的对象逃过了GC,导致了内存里面存在浮动的垃圾(float garbage)。

G1常用参数

参数/默认值含义
-XX:+UseG1GC使用 G1 垃圾收集器
-XX:MaxGCPauseMillis=200设置最大GC目标最大停顿时间为200ms,这是一个软指标。JVM会最大努力去达到它,因此有时停顿时间会达不到设置目标。G1配置是200ms
-XX:G1HeapRegionSize=n使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
-XX:InitiatingHeapOccupancyPercent=45启动并发标记标记百分比,当整堆内存使用量达到百分比时,G1使用它来触发一个基于整个堆的并发标记循环,而不仅仅是一个代。默认值是45%
-XX:NewRatio=2新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为2.
-XX:SurvivorRatio=8den/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=10设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值