盒子
盒子

JAVA进阶之JVM浅析GC

GC是JVM的垃圾收集器,它就像房间清洁工人一样。堆内存是对象主要的活动区域,JAVA中的对象分配与销毁便发生在这里,因此这里就是GC的活动区域。GC的主要工作是判断对象是否存活和回收对象,这两个工作必然有对应的方法。

判断对象存活

这个是GC要做的第一步事儿,只有死掉的对象才可以被回收。GC所采用判断对象是否存活的方法,是以Root节点为起始开始向下扫描,如果对象到这个Root没有可达的路径,便认为这个对象没用的。其中在扫描过程中,GC走过的路径叫做引用链。 使用这种方法时,必须在这一刻对象的引用固定不变的。因为在GC通过可达性来去判断对象是否存活时,如果对象的引用还在一直变化,这样最终结果的准确性就没法保证。因此通常GC在工作的时候,引用线程需要暂停一下,这也是会出现”Stop The World”这样一句话其中的一个原因。

收集方法

在判断那些对象可以回收之后,GC要做的就是去回收这些对象。回收也有很多种不同的方法,这些方法有不同的优缺点。如同清理房间一样,可以是我扔一个垃圾清理一个,也可以是我扔了很多先标记起来等到特定时间统一回收。 收集的基础算法包括标记清除算法、复制算法和标记整理算法;HotSpot采用的是分代收集算法,也就是对内存区域进行了分代,然后不同区域采用不同的算法,是对上述几种算法的组合。

标记清除方法是最基础的收集算法,这个算法首先标记出所有需要回收的对象,然后再统一回收这些被标记的对象。由于先要标记所有没用的对象,然后再统一回收,这样效率不高;其次被标记的对象可能都处在不同的内存地址,因此比较容易产生内存碎片。

复制算法是解决了上述算法的缺点。它假设有两块儿相同大小的内存,但是每次只是用其中的一块儿,当要发生GC时经过判断把存活的对象复制到另一块儿空的内存,然后清理掉使用过的那块儿内存。这个算法的缺点是牺牲一部分内存空间。HotSpot对对象分代,在年轻代就使用了复制算法。年轻代分为了一个Eden和两个Survivor,对象只在Eden上分配,发生GC时便复制Eden上还存活的对象到其中一个Survivor,始终会保持一个Survivor为空的。在HotSpot中Eden和两个Survivor的空间比例是8:1:1,这样虽然是用复制算法,但是内存的使用率达到了90%。复制算法也有缺点,如果对象的存活率较高,那么复制时的内容会比较多,且因为另一块儿内存的有限,就需要额外的内存做补充。

标记整理算法跟标记清除算法类似,只是标记需要回收的对象之后不是做清理,而是把所有存活的对象向一端移动,然后请清理掉这以外的回收的对象,降低了发生内存碎片的几率。这个算法是对复制算法缺点的一种补充。 这些回收算法都有各自的优缺点,因此可以针对不同的情况使用不同的算法。

垃圾收集器

回收算法是一种方法,垃圾收集器是对各种回收算法发的实现,可能是一个算法的实现,也可能是几种算法发组合的实现。GC收集器主要有Serial收集器、ParNew收集器、Parallel Scavenge收集器、Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器几种。

Serial收集器是最早出现的收集器,并且是单线程的(即GC是单线程的且收集器工作时应用线程需要停止)。

ParNew收集器是对Serial收集器的升级版,这个升级只是它在执行GC时是多线程的了,但是其他方面还是跟Serial一样。

Parallel Scavenge收集器是多线程且是用复制算法的收集器,这些特点与ParNew收集器一样,但它能控制收集器的吞吐量。收集器的吞吐量指的是应用线程消耗时间与程序总消耗时间的比(程序的总消耗时间包括应用线程的消耗和GC线程的消耗以及其他消耗)。这个收集器也叫吞吐量收集器。

Serial Old收集器应用于老年代,同样是一个单线程收集器。可以作为CMS收集器的后备收集器使用。

Parallel Old收集器是Parallel Scavenge收集器的老年代版,多线程且是用的是标记整理算法。此收集器是JDK1.6开始提供,JDK1.6以前由于Parallel Scavenge收集器无法与CMS收集器组合使用,所以如果新生代使用Parallel Scavenge收集器,老年代只能又Serial Old收集器一种选择。这个收集器与Parallel Scavenge收集器是最佳组合,能最大的体现Parallel Scavenge吞吐量的优势。

CMS是Concurrent Mark Sweep的缩写,这个收集器的目的是尽量缩小应用的停顿时间,这个收集器是用的标记清除算法。这个收集器由6个阶段组成,其中2个阶段需要暂停应用线程,其他4个阶段则与应用线程并行。6个步骤分别是:

  1. 初始标记:标记GC Roots能直接关联到的对象,这个阶段需要暂停应用线程,但是由于速度非常快,所以暂停时间相对比较短;
  2. 并发标记:GC Roots Tracing的过程;
  3. 并发预清理:也就是修正更新第二阶段时应用产生的对象引用;
  4. 重标记:这个阶段需要暂停应用线程,更新第三阶段发生的变化,确保清理之前对象的引用视图是正确的;
  5. 并发清理:清理掉需要被回收的对象;
  6. 并发重置:收集器收尾工作,结束掉这个GC过程,为下次GC提供干净的状态;
    G1即Garbage First,最新的垃圾收集器。对比CMS收集器,G1收集器是用标记整理算法,且对于停顿时间是可控的。通常吞吐量和暂停时间是互相矛盾的(GC标记需要被回收的对象时必须要停止应用线程,这样上下文的切换增加了JVM的开销,JVM的开销消耗了时间减小了吞吐量。想要增大吞吐量就得见可能的减少GC发生的频率,如此每次GC由于积累的垃圾太多而增大了暂停时间),G1可以在不牺牲吞吐量的情况下减小停顿时间。其原理是G1收集器对整个内存区域进行了分段,不像其他收集器一样是对整个老年代或新生代进行收集。G1把堆划分为多个固定的区域,跟踪这些区域的垃圾堆积程度,维护一个优先列表,优先回收垃圾最多的区域,进而能控制停顿时间。
支持一下
扫一扫,支持forsigner