『JavaWeb』GC简介

本篇博客简单介绍一下Java中的垃圾回收机制

为什么需要垃圾回收?


在C/C++程序中,我们知道当程序员定义了一个变量的时候,就是在内存中开辟了一段相应的空间来存值。由于内存是有限的,所以当内存不再需要使用某个变量的时候,就需要销毁该对象并释放其所占用的内存资源,好重新利用这段空间。
在C/C++中,释放无用变量内存空间的事情需要由程序员自己来处理。就是说当程序员认为变量没用了,就手动释放其占用的内存。但是这样显然非常繁琐如果有所遗漏,就可能造成资源浪费和内存泄露。当软件系统比较复杂,变量多的时候程序员往往就忘记释放内存或者在不该释放的时候释放内存了
有了GC,程序员就不需要再手动的去控制内存的释放。当JVM发觉内存资源紧张的时候,就会自动去清理无用对象所占用的内存空间。

如何判断对象已死?


Java堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经死去。判断对象是否已死有如下几种算法?

引用计数法


引用计数法就是给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已死
引用计数法实现简单,判断效率也比较高,在大部分情况下都是一个不错的算法。比如Python中就采用引用计数法来判断对象是否已死。
但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题
下面我们来看一个代码:

package gc;

public class Test {
    public Test test = null;

    public static void main(String[] args) {
        Test testA = new Test();
        Test testB = new Test();

        testA.test = testB;
        testB.test = testA;

        testA = null;
        testB = null;
    }
}

这里的testA和testB指向的对象已经没有引用在使用它,但是由于二者之间相互引用,所以引用计数器一直都是1,而不会被回收,这就是引用计数器的问题。
我们来验证一下JVM使用的是否是引用计数法,我们将上面的代码调整一下,然后看一下GC日志:

package gc;

/**
 * JVM参数:-XX:+PrintGC
 */
public class Test {
    public Test test = null;
    private byte[] bytes = new byte[1024 * 1024 * 2];

    public static void main(String[] args) {
        Test testA = new Test();
        Test testB = new Test();

        testA.test = testB;
        testB.test = testA;

        testA = null;
        testB = null;

        System.gc();
    }
}

我们点击Run,然后点击Edit Configurations…
在这里插入图片描述
然后我们加上JVM参数,点击Apply
在这里插入图片描述
运行结果如下
在这里插入图片描述
可以看到testA和testB指向的对象被回收了,也就是说JVM使用的不是引用计数法来判断对象是否已死

可达性分析算法


从前面的测试中我们可以看出,JVM使用的并不是引用计数法,JVM中使用的是可达性分析算法来判断对象是否已死
可达性分析算法是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到“GC Roots”没有任何的引用链相连时(GC Roots到这个对象不可达)时,证明此对象时不可用的。以下图为例:
在这里插入图片描述
object5、object6、object7都是不可达的,即已死对象

在JVM中,可作为“GC Roots”的对象包含下面几种

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常亮引用的对象;
  • 本地方法栈中JNI(Native方法)引用的对象

生存还是死亡
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候他们暂时处于“缓刑”阶段。要宣告一个对象的真正死亡,至少要经历两次标记的过程:如果对象在进行可达性分析之后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆写finalize()方法或者finalize()方法已经被JVM调用过,虚拟机会将这两种情况都视为没有必要执行,此时的对象才是真正死对象
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(这里所说的执行是虚拟机会触发finalize()方法)。finalize()方法是给对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象在finalize()中成功拯救自己(只需要重新与引用链上的任何一个对象建立关联即可),那在第二次标记时它将被移除出“即将回收”的集合如果对象这时候还是没有逃脱,那基本上它基本上就是被回收了

四种引用


从上面的两种判断对象已死的两种算法中,都少不了引用。
JDK1.2之前,Java中引用的定义很传统:如果引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。这种定义有些狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态
JDK1.2之后,Java对引用的概念做了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减

  • 强引用(Strong Reference):强引用指的是在程序代码之中普遍存在的,类似于Object obj = new Object();这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象实例
  • 软引用(Soft Reference):软引用是用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK1.2后提供了SoftReference类来实现软引用;
  • 弱引用(Weak Reference):弱引用也是用来描述非必须对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存到下一次垃圾回收之前当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象。在JDK1.2后提供了WeakReference类来实现弱引用;
  • 虚引用(Phantom Reference):虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被回收时收到一个系统通知。在JDK1.2后提供了PhantomReference类来实现虚引用。

垃圾回收算法


标记-清除算法


标记-清除算法是最基础的收集算法。算法分为标记和清除两个阶段:首先根据可达性分析算法标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。后续的回收算法都是基于这种思路并对其不足加以改进而已。
在这里插入图片描述
标记-清除算法有两个缺点

  • 效率问题:标记和清除这两个过程的效率都不高
  • 空间问题:标记-清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾回收

复制算法


为了解决标记-清除的效率问题,出现了复制算法。复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块当这块内存需要进行垃圾回收时,会将此区域还存活的对象复制到另一块上面,然后再把使用过的内存区域一次性清理掉。这样做的好处是每次都是对整个半区进行垃圾回收,内存分配也就不再考虑内存碎片化等复杂情况,只需要移动堆顶指针,按顺序分配即可。
在这里插入图片描述

标记-整理算法


标记-整理算法和标记-清除过程一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存
在这里插入图片描述

分代收集算法


当前JVM垃圾收集采用的都是分带收集算法,这个算法没有新思想,只是根据对象存活的周期不同将内存划分为几块
一般是将Java对分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高,就可以采用标记-清除算法或标记-整理算法
在这里插入图片描述
新生代中98%的对象都是“朝生夕死”的,所以不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,一个称为To区)。当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间
Survivor空间不足时,需要依赖其他内存(老年代)进行分配担保,放不下的对象直接存入老年代中。除此之外,对象进入老年代还有一种方法:当新生代中对象经历过一定次数的垃圾回收之后还没有被回收掉也会进入老年代,一般阈值为15。
我们来看一下Hotspot实现的复制算法流程

  1. 当Eden区满的时候,会触发第一次Minor GC,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor GC时,会扫描Eden和From区,对两个区域进行垃圾回收,经过这次回收还存活的对象,则直接复制到To区,并将Eden和From区清空;
  2. 当后续Eden又发生Minor GC的时候,会将存活的对象复制到From区,并将Eden和To区清空;
  3. 部分对象会在From和To区域中来回复制,如此交换达到一定次数(由JVM参数MaxTenuring Threshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。

Minor GC和Full GC

  • Minor GC也叫新生代GC:指的是发生在新生代的垃圾回收。因为Java对象大多数具有朝生夕死的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快
  • Full GC也叫老年代GC或者Major GC:指发生在老年代的垃圾回收,出现了Full GC,经常会伴随着至少一次Minor GC,Full GC一般会比Minor GC慢10倍以上

回收方法区


方法区的垃圾回收主要收集两部分内容:废弃常亮和无用的类

回收废弃常亮和回收Java堆中的对象十分类似。以常量池中字面量的回收为例,假如一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象引用常量池中的“abc”常量,也没有在其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个“abc”常亮将会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

判定一个类是否是“无用类”相对复杂很多。类需要同时满足下面三个条件才会被算是“无用的类”

  1. 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例);
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集器


如果上面所说的收集方法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。以下讲的收集器基于JDK 1.7的G1收集器之后的HotSpot虚拟机,这个JVM包含的所有收集器如下图所示:
在这里插入图片描述
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们之间可以搭配使用。所处的区域,表示它是属于新生代收集器还是老年代收集器。在将具体的收集器之前我们先来明确三个概念:

  • 并行(Parallel):指多条垃圾回收线程并行工作,用户线程仍处于等待状态
  • 并发(Concurrent):指用户线程与垃圾回收线程同时执行(不一定并行,可能会交替执行),用户程序继续运行,而垃圾回收程序在另外一个CPU上;
  • 吞吐量:就是CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))。如:虚拟机总共运行了100分钟,其中垃圾回收花掉1分钟,那吞吐量就是99%。

Serial收集器(新生代收集器,串行GC)


Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3之前)是虚拟机新生代唯一的选择。

  • 特性:这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)
  • 应用场景:Serial收集器是虚拟机运行在Client模式下的默认新生代收集器
  • 优势简单而高效,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
  • 工作流程图
    在这里插入图片描述

ParNew收集器(新生代收集器,并行GC)


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

  • 特性:Serial收集器的多线程版本。
  • 应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。
    作为Server模式下的首选收集器之中有一个和性能无关的很重要的原因除了Serial收集器外,目前只有它能与CMS收集器配合工作
    JDK 1.5时期HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并行收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。不幸的是,CMS作为老年代收集器,却无法与JDK 1.4中已经存在的新生代收集器Parallel Scavenge配合工作,所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial中的一个。
  • 对比分析ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百的保证可以超越Serial收集器。
    但是随着可以使用的CPU数量的增加,它对于垃圾收集时系统资源的有效利用还是很有好处的
  • 工作流程图
    在这里插入图片描述

Parallel Scavenge收集器(新生代收集器,并行GC)


  • 特性:Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器
    使用两个参数控制吞吐量XX: MaxGCPauseMillis(控制最大垃圾收集停顿时间)、XX: GCRatio(直接设置吞吐量的大小)。
    直观上,只要最大的垃圾收集停顿时间越小,吞吐量是越高的,但是GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。比如原来10秒收集一次,每次停顿100毫秒,现在变成5秒收集一次,每次停顿70毫秒。停顿时间下降的同时,吞吐量也下降了。
  • 应用场景停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运行任务,主要适合在后台运算而不太需要太多交互的任务
  • 对比分析
    Parallel Scavenge收集器 VS CMS收集器:Parallel Scavenge收集器关注点是达到一个可控制的吞吐量CMS收集器关注点是尽可能的缩短在垃圾收集时用户线程的停顿时间
    Parallel Scavenge收集器 VS ParNew收集器:二者有一个重要的区别就是Parallel Scavenge收集器具有自适应调节策略
  • GC自适应调节策略:Parallel Scavenge收集器有一个参数xx:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手动指定新生代的大小、Eden区与Survivor区的比例、晋升老年代对象年龄参数等细节参数了,虚拟机会根据当前系统的运行状况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
  • 工作流程图:同ParNew收集器。

Serial Old收集器(老年代收集器,串行GC)


  • 特性:Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法
  • 应用场景
    Client模式:Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用;
    Server模式:如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5及之前的版本中与Parallel Scavenge收集器搭配使用;另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
  • 工作流程图
    在这里插入图片描述

Parallel Old收集器(老年代收集器,并行GC)


  • 特性:Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程标记-整理算法
  • 应用场景:在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge和Parallel Old收集器
    这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。知道Parallel Old收集器出现后,“吞吐量优先”收集器终于有了名副其实的应用组合。
  • 工作流程图
    在这里插入图片描述

CMS收集器(老年代收集器,并发GC)


  • 特性:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
    CMS收集器是基于标记-清除算法实现的,它的运行过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤
    • 初始标记(CMS initial mark):初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要Stop The World
    • 并发标记(CMS concurrent mark):并发标记阶段就是进行GC Roots Tracing的过程;
    • 重新标记(CMS remark):重新标记阶段是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要Stop The World
    • 并发清除(CMS concurrent sweep):并发清除阶段会清除对象
  • 优点:CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停顿
  • 缺点
    • CMS收集器对CPU资源非常敏感,其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数是(CPU数量 + 3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集器线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大;
    • CMS无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了;
    • CMS收集器会产生大量空间碎片,CMS是一款基于标记-清除算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC
  • 工作流程图
    在这里插入图片描述

G1收集器(唯一一款全区域的垃圾回收器)


G1(Garbage First)垃圾回收器是用在heap memory很大的情况下,把heap划分为很多很多的region块,然后并行对其进行垃圾回收。
G1垃圾回收器在清除实例所占用的内存后,还会做内存压缩
G1垃圾回收器回收region的时候基本不会Stop The World,而是基于most garbage优先回收(整体来看是基于标记-整理算法,从局部(两个region之间)看基于复制算法)的策略来对region进行垃圾回收的。如下图所示:
在这里插入图片描述
一个region有可能属于Eden,Survivor或者Tenured内存区域。图中的E表示该region属于Eden内存区域,S表示属于Survivor内存区域,T表示属于Tenured内存区域。图中空白的表示未使用的内存空间。G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。这种内存区域主要用于存储大对象,即大小超过一个region大小的50%的对象。

年轻代垃圾收集
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法把Eden区和Survivor区的存活对象复制到新的Survivor区域,如下图:

老年代垃圾收集
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同

  • 初始标记(Initial Mark):同CMS垃圾收集器的initial mark阶段一样,G1也需要暂停应用程序的执行,它会标记从根对象出发,在根对象的第一层孩子结点中标记所有可达的对象。但是G1垃圾收集器的initial Mark阶段是跟Minor GC一同发生的。也就是说,在G1中,你不用像在CMS那样,单独暂停应用程序的执行来运行initial Mark阶段,而是在G1触发minor GC的时候一并将老年代上的initial Mark给做了
  • 并发标记(Concurrent Mark):在这个阶段G1做的事情跟CMS一样。但G1同时还多做了一件事情,就是如果在Concurrent Mark阶段中,发现哪些Tenured region中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段。这也是Garbage First名字的由来。同时,在该阶段,G1会计算每个region的对象的存活率,方便后面的clean up阶段使用
  • 最终标记(CMS中的remark阶段):在这个阶段G1做的事情和CMS一样,但是采用的算法不同,G1采用一种叫做SATB(snapshot-at-the-beginning)的算法能够在Remark阶段更快的标记可达对象;
  • 筛选回收阶段(Clean up/Copy)在G1中,没有CMS中对应的sweep阶段。相反,它有一个clean up/copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是和minor GC一同发生的,如下图所示:
    在这里插入图片描述

G1(Garbage-First)是一款面向服务端应用的垃圾收集器HotSpot开发团队赋予它的使命是未来可以替换掉JDK 1.5中发布的CMS收集器。如果你的应用追求低停顿,G1可以作为选择;如果你的应用追求吞吐量,G1并不带来特别明显的好处。

理解GC日志


每一种垃圾收集器的日志形式都是由它们自身的实现所决定的,换而言之,每个收集器的日志格式都可以不一样。但虚拟机设计者为了方便用户阅读,将各个收集器的日志都维持一定的共性,例如以下两段典型的GC日志:

[GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]  
[Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm : 2999K->2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

GC日志开头的[GC[Full GC说明了这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。如果有Full,说明这次GC发生了STW

例如下面这段新生代收集器ParNew的日志也会出现[Full GC(这一般是因为出现了分配担保失败之类的问题,所以才导致STW)。如果是调用System.gc()方法所触发的收集,那么这里将显示[Full GC(System)

[Full GC 283.736: [ParNew: 261599K->261599K(261952K), 0.0000288 secs]

接下来出现的[DefNew[Tenured[Perm表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的,例如上面样例所使用的Serial收集器中的新生代名为"Default New Generation",所以显示的是[DefNew。如果是ParNew收集器,新生代名称就会变为[Parnew,意为"Parallel New Generation"。如果采用的是Parallel Scavenge收集器,那它配套的新生代称为"PSYoungGen",老年代和永久代同理,名称也是由收集器决定的。

后面方括号内部"3324K->152K(3712K)"含义为:GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)
而在方括号外部"3324K->152K(11904K)"表示:GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)

再往后,"0.0025925 secs"表示该内存区域GC所占用的时间,单位是秒。有的收集器会给出更具体的时间数据,如:[Times: user=0.01 sys=0.00, real=0.02 secs],这里面的user、sys、real与Linux的time命令所输出的时间含义一致,分别代表用户消耗的CPU时间内核态消耗的CPU时间操作从开始到结束所经过的墙钟时间(Wall Clock Time)
CPU时间和墙钟时间的区别是,墙钟时间包括各种非运算的等待耗时,例如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或多核的话,多线程操作会重叠这些CPU时间,所以读者看到user或sys时间超过real时间是完全正常的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值