5 垃圾收集器与内存分配

来源:互联网 发布:500px哪些摄影师 知乎 编辑:程序博客网 时间:2024/06/08 02:34

5垃圾收集器与内存分配

串行回收器

串行回收器是指使用单线程进行垃圾回收的回收器。串行回收器可以在新生代和老年代使用,根据作用于不同的堆空间,分为新生代串行回收器和老年代串行回收器。

新生代串行回收器

主要有两个特点
1. 它仅仅使用单线程进行垃圾回收
2. 它是独占式的垃圾回收

新生代串行回收器使用复制算法,实现相对简单。

使用-XX:+UseSerialGC参数可以指定使用新生代串行收集器和老年代串行收集器。当虚拟机在Client模式下运行时,它是默认的垃圾收集器。

老年代串行回收器

老年代串行收集器使用的是标记压缩算法。和新生代收集器一样,它也是一个串行的,独占式的垃圾回收器。

并行回收器

新生代ParNew回收器

它只是简单的将串行回收器多线程化,它的回收策略,算法和参数与新生代串行收集器一样。

ParNew回收器在工作时的线程数量可以使用-XX:ParallelGCThreads参数指定。一般,最好的cpu数量相当。

新生代ParallelGC回收器

新生代ParallelGC收集器也使用复制算法的收集器。但是,ParallelGC回收器有一个重要的特点,它非常关注系统的吞吐量。

新生代ParallelGC回收器可以使用以下参数启用。

-XX:+UseParallelGC 新生代使用ParallelGC收集器,老年代使用串行回收器。

-XX:+UseParallelOldGC 新生代使用ParallelGC收集器,老年代使用ParallelOldGC回收器。

ParallelGC回收器提供两个非常重要的参数用于控制系统的吞吐量。

-XX:MaxGCPauseMillis 设置最大垃圾收集停顿时间。它的值是一个大于0的整数。如果希望减少停顿时间,就把这个值设的很小,为了达到预期的停顿时间,虚拟机可能会使用一个较小的堆,而这将导致垃圾回收变得很频繁。

-XX:GCTimeRatio
设置吞吐量大小。它是一个0到100之间的整数。假设GCTimeRatio的值为n,则系统将花费不超过1/(1+n)的时间进行垃圾收集,默认值为99,即不超过1%的时间用于垃圾收集。

此外,ParallelGC回收器与ParNew的另一个不同在于它还支持自适应的GC调节策略。使用-XX:+UseAdaptiveSizePlicy可以打开自适应GC策略。在这种模式下,新生代大小,eden和survivor的比例,晋升老年代的对象年龄参数都会被自动调整,以达到堆大小,吞吐量和停顿时间之间的平衡点。

在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆,目标吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMillis),让虚拟机自己完成调优工作。

老年代ParallelOldGC回收器

ParallelOldGC回收器使用标记压缩算法,它在jdk1.6中才可以使用。

使用-XX:+UseParallelOldGC可以在新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器。这是一对非常关注吞吐量的垃圾回收器的组合。

CMS回收器

与ParallelGC和ParallelOldGC不同,CMS回收器主要关注系统的停顿时间。CMS是Concurrent Mark Sweep的缩写,意为并发标记清除,从名称上就可以得知,它使用标记清除算法,同时它又是一个使用多线程并行回收的垃圾收集器。

CMS主要工作步骤

主要步骤有:初始标记,并发标记,预清理,重新标记,并发清除和并发重置。其中初始标记和重新标记是独占系统资源的。

在整个CMS回收过程中,默认情况下,并发标记后会进行一个预清理操作。由于重新标记是独占CPU的,如果新生代GC发生后,立即触发一次重新标记,那么一次停顿时间可能会很长。为了避免这种情况,预处理时,会刻意等待一次新生代GC的发生,然后根据历史性能预测下一次新生代GC发生的时间,然后在当前时间和预测时间的中间时刻进行重新标记,从最大程度上避免新生代GC和重新标记重合,尽可能减少一次停顿时间。

CMS主要设置参数

启用CMS回收器的参数是-XX:+UseConcMarkSweepGC

CMS默认启动的并发线程数是(ParallelGCThreads+3)/4

ParallelGCThreads表示GC并行时使用的线程数量。

CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一个阈值时便开始进行回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。

阈值可以由-XX:CMSInitiatingOccupancyFraction来指定,默认为68,即老年代的空间使用率达到68%时,会执行一次CMS回收。如果在CMS执行过程中,已经出现内存不足的情况,那么CMS回收就会失败,虚拟机将启用老年代串行收集器进行垃圾回收。

CMS是一个基于标记清除算法的回收器。CMS回收提供几个用于内存压缩整理的参数。-XX:+UseCMSCompactAtFullCollection开关可以使CMS在垃圾收集完成后,进行一次内存碎片整理,内存随便的整理不是并发进行的。-XX:CMSFullGCsBeforeCompaction参数可以用于设定进行多少次CMS回收后,进行一次内存压缩。

G1收集器

G1收集器是在jdk1.7中正式使用的全新的垃圾回收器。从分带上看,G1依然属于分代垃圾回收器,它使用分区算法,所以并不要求整个eden区,年轻代或者老年代都连续。

特点如下:
1. 并行性:多个GC线程同时工作
2. 并发性:G1拥有与应用程序交替执行的能力。
3. 分代GC:G1依然是一个分代收集器,但是和之前回收器不同,它同时兼顾年轻代和老年代。
4. 空间整理:G1在回收过程中,会进行适当的对象移动,减少空间碎片。
5. 可预见性:由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小的回收的范围,对全局停顿也能得到较好的控制。

G1收集过程可能有4个阶段:
- 新生代GC
- 并发标记周期
- 混合收集
- 如果需要,进行Full GC

新生代GC

新生代GC的主要工作是回收eden区和survivor区

并发标记周期

  • 初始标记:标记从根节点直接可达的对象。这个结算会伴随一次新生代GC,它是会产生全局停顿的,应用程序在这个阶段必须停止执行。
  • 根区域扫描:在这个阶段,将扫描survivor区直接可达的老年代区域,并标记直接可达的对象。这个过程可以和应用程序并发执行,但是根区域扫描不能和新生代GC同时执行。
  • 并发标记:和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记,这是一个并发的过程,并且这一过程可以被新生代GC打断。
  • 重新标记
  • 独占清理:这个阶段会引起停顿。它将计算各个区域的存活对象和GC回收比例并进行排序,识别可供混合回收的区域。该阶段给出了需要被混合回收的区域并进行了标记,在混合回收阶段,需要这些信息。
  • 并发清理阶段:识别并清理完全空闲的区域。它是并发的,不会引起停顿。

混合回收

G1全称Garbage First Garbage Collector,直译为垃圾优先的垃圾回收器,这里的垃圾优先指的是回收时优先选出垃圾比例最高的区域。

这个阶段叫做混合回收,因为在这个阶段,既会执行正常的年轻代GC,又会选取一些被标记的老年代区域进行回收。被清理区域中的存活对象会被移动到其他区域。

混合GC可能会执行多次,直到收集了足够的内存空间,然后,它会触发一次新生代GC。之后又可能发生一次并发标记周期的处理。

必要时进行FullGC

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

比如,当G1在进行并发标记时,由于老年代被快速填充,那么G1会终止并发标记而转入一个Full GC。

G1相关参数

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

对G1收集器进行设置时,最重要的一个参数就是-XX:MaxGCPauseMillis,它用于指定目标最大停顿时间。

另外一个重要参数是-XX:ParallelGCThreads,它用于设置并行回收时,GC的工作线程数量。

此外,-XX:InitiatingHeapOccupancyPercent参数可以指定这个堆使用率达到多少时,触发并发标记周期的执行。默认值是45.

对象进入老年代

默认MaxTenuringThreshold为15,即第16次GC时对象进入老年代。MaxTenuringThreshold指的是最大晋升年龄。它是对象晋升老年代的充分非必要条件。即达到该年龄必然晋升,而未达到该年龄,对象也有可能晋升。

在TLAB上分配对象

TLAB全称是Thread Local Allcation Buffer ,即线程本地分配缓存。从名字上可以看到,TLAB是一个线程专用的内存分配区域。

TLAB本身占用了eden区的空间。在TLAB启用的情况下,虚拟机会为每个java线程分配一块TLAB空间。

方法finalize()对垃圾回收的影响

每一个即将被回收并且包含有finalize()方法的对象都会在被正式回收前加入FinalizerThread的执行队列,队列中的每一项Finalizer都封装了一个引用referent,这个引用指向实际要被回收的对象,所以在finalize()方法执行完之前,对象都不能回收,所以一个糟糕的finalize()方法可能会导致大量对象无法被清理。