JVM 垃圾回收(Garbage Collection)

什么是垃圾?

在日常生活中,我们通常把垃圾丢到垃圾桶里然后倒掉。 在JVM中垃圾的是指内存中不在被使用到的对象,而回收是指把垃圾“倒掉”。

如果垃圾长时间不被回收,就会导致垃圾对象一直占用内存空间直至程序终止。

当内存中存在大量无法被使用的垃圾对象从而有可能的导致内存溢出。

如何判断什么时候对象属于垃圾?

名词解释:

  • 可达对象:通过根对象进行引用搜索,最终可以达到的对象。
  • 不可达对象:通过根对象进行引用搜索,最终没有被引用到的对象
  • 可触及对象:从根节点出发可以到达该对象
  • 可复活对象:该对象的所有引用都释放,但是在fianlize()函数中复活。
  • 不可触及对象:对象的finalize()函数调用,并且没有被复活时将进入不可触及状态,不可触及对象不可能在复活。fianlize()函数只会被调用一次。

垃圾回收处理的方式

垃圾回收算法

标记清除

标记清除是现代垃圾回收算法的基础,它分为了两个阶段:标记阶段、清除阶段。

标记阶段:通过根节点开始标记可达对象,未被标记的对象视为垃圾对象。

清除阶段:清除为未被标记的对象

弊端:标记清除算法可以有效的处理垃圾,但是也带来内存碎片化的问题,因为清除阶段是不连续的清除。

标记压缩\整理

标记压缩是在标记清除的基础上解决了内存空间不连续的问题,该回收算法的出发点是针对长时间都是处于可达对象时,需要清理个别不可达对象。核心思想是在清除阶段将可达对象移到内存空间的另一边。再将不可达对象清理。

优点:解决了标记清除带来的弊端,因此适用于老年代,因为老年代中的对象大多数情况下是处于对象可达,当需要回收时采用该算法的效率高

缺点:在对象频繁处于创建、回收情况下该算法效率低。

分区

分代算法是将对象按生命周期划分而分区是将整个堆空间划分连续的不同大小的内存空间。

一般情况下内存空间越大回收时间越长,从而停顿时间较长。为了更好的控制GC的停顿时间,

一块大的内存区域划分为多个小块。根据目标的停顿时间,每次合理的选择小块内存进行回收,从而减少GC停顿时间。

分代

分代的思想是将所有的垃圾回收器划分为新生代和老年代,然后根据不同代的特性选择不同的回收算法。

对于新生代、老年代而言,通常情况下新生代回收频率较高耗时短而老年代的回收频率低但是耗时长。

为了支持高频率的新生代回收。可能使用了一个卡表(card table) 的数据结构。卡表为一个比特位集合

每一个比特位用来表示老年代的每一区域是否持有新生代对象的引用。如有则为1,为0表示无。

在新生代GC时,会扫描一下卡表看看是否有1的老年代空间。

 复制

复制的核心思想是将内存空间一份为二每次只使用一半,当进行垃圾回收时从使用的内存空间将可达对象转移到空闲的内存空间,然后清除垃圾对象。

优点:复制算法完美的解决了标记清除算法带来的弊端,内存空间是连续可用的,当对象大多数处于不可达时该算法的效率很高

弊端:内存空间一分为二,降低了可用内存空间会提升复制算法的调用频率

JVM 堆区采用垃圾回收算法的具体实现

名词解释

  • 新生代:存放年轻对象的堆空间。年轻对象是指刚刚创建的对象或者经历垃圾回收次数不多的对象。
  • 老年代:存放老年对象的堆空间。老年对象是指经历多次垃圾回收后依然可达的对象。

堆空间采用分代算法划分为新生代、老年代

新生代采用的是复制算法,内部划分为:eden、from、to空间。其中from、to空间是两块相同大小、地位相等、且角色可以互换的空间

其中from、to合称为survivor空间(存放可达对象的空间)

老年代:可以采用标记清除、标记压缩算法。

 

0

JVM 线程停顿(STW:stop the world)

当内存不足时,作为守护线程的垃圾回收线程被唤醒。

在垃圾回收线程执行任务时,JVM将其他线程都先暂停防止新的垃圾产生。

这个过程中JVM暂停除垃圾回收线程外的其他线程被称为线程停顿

线程停顿期间所有请求都无法执行、停止响应因此也叫STW。

垃圾收集器

名词解释:

  • 并发:是指收集器和应用线程交替执行,并行是指应用程序停止,同时由多个GC线程一起执行GC,因此并行回收器不是并发,因为并行回收器在回收时应用程序完全挂起,不存在交替执行步骤。
  • 根节点:是指在GC查询垃圾对象的开始目标,通常包括:方法区的静态变量的引用对象、常量的引用对象、线程栈的局部变量表的引用对象、本地方法Native的引用对象。

串行垃圾回收器

串行回收器是指使用单线程进行垃圾回收的回收器。每次回收时,串行回收器只有一个工作线程,对于并行能力较弱的计算机来说,串行回收器的专注性和独占性有更好的性能表现。

串行回收器可以在新生代和老年代使用,根据作用于不同的堆空间分为新生代串行回收器和老年代串行回收器。

工作原理:串行回收器运行时,应用程序中的所有程序都停止工作,进行等待。在实时性要求较高的应用较高的场景中是不能被接受的

  1. 新生代串行回收器:jdk中最基本的垃圾回收器之一,串行回收器主要有两个特点:a、他仅仅使用单线程进行垃圾回收, b、他是独占式的垃圾回收
  2. 老年代串行回收器:和新生代串行收集器一样,串行、独占方式所以在老年代进行垃圾回收时时间会比新生代时间更长,一旦老年代回收器启动应用程序会停顿较长的时间

通常老年代串行收集器会配合新生代的其他形式的回收器。例如:-XX:+UseParNewGC、-XX:+UseParallerGC。

并行垃圾回收器

工作原理:在串行回收器的基础上,它使用了多个线程同时进行垃圾回收,对于并行能力较强的计算机上,可以有效缩短垃圾回收所用的时间

  1. 新生代(ParNewGC):在回收策略、算法、参数与新生代串行回收器一样,只不过回收时间在并行能力较强的CPU上要短于串行回收器
  2. 新生代(ParallerGC):与ParNewGC一样但它非常关注系统的吞吐量。在使用该收集器的场景下还可以通过-XX:+UseAdaptiveSizePolicy 让虚拟机自动调整GC策略
  3. 老年代(ParallerOldGC):jdk1.6之后提供是它同样关注系统的吞吐量,在对吞吐量敏感的系统中可以考虑使用。

CMS垃圾回收器(Concurrent Mark Sweep)

CMS是一个非独占、多线程、并发回收,只关注GC的停顿时间的垃圾收集器

工作步骤:

  1. 初始标记
  2. 并发标记
  3. 预清理(并发)
  4. 重新标记
  5. 并发清理
  6. 并发重置

回收过程:

根据初始化标记、并发标记、重新标记查找出需要回收的对象,并发清理阶段正式回收对象,并发重置阶段在垃圾回收完成之后重新的初始化CMS的数据结构,为下一次垃圾回收做好准备(并发阶段可以随应用程序一起执行)

回收触发条件:

默认情况下,在并发标记后会有一个预清理步骤(-XX:-CMSPrecleaningEnabled 可以关闭预处理),为真正的回收做检查和准备。

预处理时,会刻意的等待一次新生代GC的发生,然后根据历史数据预判下次GC可能发生的时间。然后在当前时间和预测时间的中间时刻,

进行重新标记,这样做的好处可以最大程度上避免新生代GC与重新标记重合,尽可能减少一次GC停顿时间。

面临的问题:

  1. 当CPU资源紧张时,受到CMS回收线程数的影响,应用系统的性能在垃圾回收阶段可能会变的非常糟糕。
  2. 在回收阶段以非独占方式回收,应用程序还在不停的工作会产生新的垃圾,这些新的垃圾在CMS并发清理阶段是无法被清理的。
  3. 还要保证有足够内存来维持应用程序的运行
  4. 标记清除的缺点就是会产生内存碎片
  5. 当内存使用率过快,CMS回收阶段因为内存不足导致失败,老年代将退化为串行回收器。

G1垃圾回收器 (Garbage First Garbage Collector)

jdk1.7推出全新的垃圾回收器,从长期发展来看是为了替代CMS回收器,它采用了分代回收算法,它划分为新生代、老年代。

堆区仍然保留eden、survivor区。但从堆区结构来看它并不要求eden、年轻代、老年代连续。它还采用了分区算法,它具备以下特点:

  1. 并行性:G1回收期间,它可由多个线程同时工作、有效理由多核计算能力。
  2. 并发性:G1拥有与应用程序交替执行的能力,部分工作可以与应用程序同时执行,因此一般来说,不会在整个回收期间完全阻塞应用程序。
  3. 分代GC:G1依然是一个分代收集器,但是和之前回收器不同,它同时兼顾年轻代和老年代对比其他回收器,它们或者工作在年轻代、或者工作在老年代
  4. 空间整理:G1回收过程中,会进行适当的对象移动,不像CMS只是简单的标记清除对象,在若干次GC后CMS必须进行一次碎片整理而G1不同,它每次回收都会有效的复制对象,减少空间碎片。
  5. 可预见性:由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围。因此对于全局的停顿也能得到较好的控制。

收集过程

  • 新生代GC

当eden区被占满了新生代GC就会发生,新生代的回收对象是eden区、survivor区,回收后eden区将被全部清空,survivor区至少会保留一个。

新生代GC的老年代区域增多。

  • 并发标记周期

并发阶段与CMS类似,它们都是为了降低GC停顿时间,而将可以和应用程序并发的部分单独提取出来执行。

  1. 初始标记:标记才能够根节点直接可达的对象,这个阶段会伴随一次新生代GC,它会产生全局停顿,应用程序在这个阶段必须停止执行。
  2. 根区域扫描:由于初始标记必然会伴随一次新生代GC,所以在初始化标记后,eden被清空,并且存活对象被移入survivor区。在这个阶段,将扫描有suvivor区直接可达老年代区域,并标记这些可达对象,这个过程是可以和应用程序并发执行的但是根区域扫描不能和新生代GC同时执行,因为根区域依赖survivor的对象而新生代GC会修改这个区域。
  3. 并发标记:和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记。这是一个并发的过程并且这个过程可以被一次新生代GC打断。
  4. 重新标记:和CMS一样,重新标记也是会产生应用程序停顿的。由于在并发标记过程中,应用程序依然在运行,因此标记结果可能需要进行修正。所以在此对上一次的标记结果进行补充。在G1中,这个过程是用STAB (snapshot-at-the-beginning)算法完成。
  5. 独占清理:这个阶段是会引起停顿的他将计算各个区域的存活对象和GC回收比例并进行排序,识别可供混合回收的区域。在这个阶段还会更新记忆集(remebered set),该阶段给出了需要被混合回收的区域并进行了标记,在混合回收阶段需要这些信息。
  6. 并发清理:这里会识别并清理完全空闲的区域。它是并发的清理,不会引起停顿。

 

0

  • 混合收集

在并发周期中,虽然有部分对象被新生代GC回收,但是总体上来说,回收比例是相当低的。但是在并发标记周期后,G1已经明确知道哪些区域含有比较多的垃圾对象,

在混合回收阶段,就可以专门对这些区域进行回收。当然G1会优先回收垃圾比例较高的区域,因为回收这些区域的性价比也比较高;

G1是以垃圾优先的垃圾回收器。之所以叫混合回收是因为在这个阶段即会执行正常的年轻代GC,又会选取一些被标记的老年代区域进行回收,它同时处理了新生代和老年代。

  • 必要时的Full GC

和CMS类似,并发收集由于让应用程序和GC线程交替执行,因此总是不能完全避免在特别繁忙的场合会出现在回收过程中内存不足的情况。当遇到这种情况,G1也会转入一个Full GC进行回收。

比如:

当G1在并发标记时,老年代快速填充,那么G1会终止并发标记而转入一次Full GC;

当混合GC时发生空间不足或者在新生代GC时,survivor区和老年代无法容纳幸存对象,也会导致Full GC 发生。

G1回收日志
0

垃圾回收器虚拟机参数一览表

垃圾回收器

回收算法

jvm 参数及说明

附加参数及说明

串行回收器

新生代:复制

老年代:标记压缩

-XX:+UseSerialGC:设置新生代、老年代采用单线程串行回收

并行回收器

新生代:复制

老年代:标记压缩

-XX:+UseParNewGC:新生代采用多线程、独占回收,老年代采用串行化回收器

-XX:+UseParallerGC:新生代采用多线程、独占回收关心吞吐量

-XX:+UseParallerOldGC:新生代采用多线程、独占、高吞吐量,老年代采用多线程、并发、独占

-XX:+UseConcMarkSweepGC:新生代采用ParNew,老年代采用CMS回收器

-XX:ParallerGCThreads:设置ParNew并行线程数

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

-XX:GCTimeRatio:设置吞吐量的大小,1-100整数。

CMS回收器

标记清除

-XX:+UseConcMarkSweepGC:新生代采用ParNew,老年代采用CMS回收器

-XX:ConcGCThreads:设置并发线程,默认:(ParallerGCThreads

+3)/4

-XX:-CMSPrecleaningEnabled:可以关闭预处理

-XX:CMSInitiatingOccupancyFraction: 老年代空间达到多少进行回收,默认:68%

-XX:+UseCMSCompactAtFullConllection: 在垃圾回收后进行一次压缩

-XX:CMSFullGCsBeforeCompaction:经历多少次CMS后压缩一次

-XX:+CMSClassUnloadingEnabled: 是否开启回收Perm区域

G1回收器

分区

-XX:+UseG1GC:打开G1收集器开关

-XX:MaxGCPauserMillis:设置指定目标最大停顿时间。如何一次停顿时间大于该时间都会调整堆空间。

-XX:ParallerGCThreads:设置ParNew并行线程数

-XX:InitiatingHeapOccupancyPercent:堆空间达到多少后触发并发标记周期(默认:45%),一旦设置G1不会试图修改,当该值过大会导致并发标记阶段迟迟不被触发,Full GC可能性大,该值过小会导致并发周期频繁,大量GC线程抢占CPU导致性能下降。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值