JVM(02):垃圾回收和内存分配策略

一、内存分配与回收策略

    对象的内存分配,可以理解为在堆内存上分配内存空间,有时候也会经过JIT编译后被拆散成标量类型,分配在栈内存中。这里主要介绍堆内存的分配。
    对象一般会被分配在新生代的Eden区,有些对象也会被直接分配在老年代,这主要看相关细节设置。

1、对象优先被分配在Eden区

    对象首先会被在Eden区进行内存分配,如果Eden区空间不足,则会进行一次Minor GC,然后将剩下的对象转移到Survivor区,如果Survivor区的空间不能够放下Eden区进过GC后依然存活的对象,则会利用担保机制,将对象转移到老年代,如果担保失败,则会触发一次Full GC。

2、大对象直接进入老年代

    所谓大对象就是指那些需要大量连续空间的Java对象,比如大字符串、大数组等。可以通过-XX:PretenureSizeThreshold参数来设置大对象的阈值。
    这样做的目的是避免Eden区和Survivor区之间发生大量的内存复制。

3、长期存活的对象将进入老年代

    JVM采用了分代收集的思想。当Eden区的对象熬过一次Minor GC将被转移到Survivor区,并且年龄会增加一岁,并且之后每经过一次Minor GC年龄都会增加一岁,JVM默认对象的年龄是15,超过15岁会被移到老年代,可以通过设置-XX:MaxTenuringThreshold来设置对象移入到老年代的阈值。

4、动态对象年龄判断

    Survivor区的对象并不一定会打到15岁才被移动到老年代。如果Survivor中相同年龄的所有对象所占空间之和大于当前Survivor空间的一半,那么包括当前年龄的所有对象都会被转移到老年代。

5、空间分配担保

    在MinorGC之前,虚拟机首先检查老年代中最大的连续空间是否能够存放下新生代的所有对象,如果可以则进行Minor GC,如果老年代中的可用空间无法满足新生代对象的所需空间,则检查是否配置了允许担保参数:HandlePromotionFailure。如果允许,那么会继续检查老年代中最大的可用空间是否大于历次晋升到老年代对象的平均大小,如果大于则担保成功进行Minor GC,如果担保失败或者不允许担保,则会进行一次Full GC。

二、如何判断哪些对象已死。

1、程序计数器

    程序计数器是给对象中添加一个引用计数器。每当有一个地方引用他,计数器的值就+1;当引用失效时计数器的值就-1。当计数器的值为0时就是不可再被使用的。但是这种方式无法解决对象间的相互引用。因此,虚拟机并不是采用这种方式。因为他很难解决对象之间循环引用的问题。

2、根搜索法

    现在主流的虚拟机大都采用这种算法。他的思想是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所有走过的路径成为引用链(Reference Chain),当以个对象到GC Roots没有任何引用链(即对象到GC Roots 不可达)时,就说明该对象是不可用的。

3、强软弱虚引用

    JDK1.2之后,对引用概念进行了扩充。引入强引用、软引用、弱引用、虚引用等概念。
  • 强引用:类似于Object obj = new Object()等普遍存在于系统中,强引用只要存在,垃圾回收器就不会回收。
  • 软引用:描述一些有用但是不是必须存在的引用,在系统出现内存溢出之前,会将软引用进行回收释放空间,如果释放后还是无法满足内存空间需要,则会抛出内存溢出异常。软引用用处较多,比如浏览器的后退按钮,后退是可短暂记录上次的浏览信息,空间不足是可被回收,释放空间。
  • 弱引用:描述一些非必须对象,当系统GC的时候,无论空间是否足够,都会回收该引用的对象。
  • 虚引用:也成为幽灵引用,它的存在完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例。它存在的唯一目的就是当这个对象被回收的时候能够收到一个系统通知。

4、判断对象已死

    即使可达性分析算法中不可达的对象也不一定就“非死不可”,这个时候有一个“缓刑”的过程,也就是二次标记。当一个对象不可达的时候,会对其进行一次标记,如果一个对象被标记两次,就真的死了。当对象被一次标记后,可以在finalize()方法中实现自救,但是一个对象的finalize()只能被调用一次,也就是说一个对象只能自救一次。

4.1、第一次标记并进行筛选

    当一个对象经过可达性分析算法后被判断已死,此时会验证该对象有没有实现finalize(),如果有实现,则调用finalize()方法,并对该对象进行一次标记。如果该对象没有实现finalize()方法,则直接回收该对象。

4.2、第二次标记

    如果一个对象实现了finalize()方法,在第一次标记时该对象可在finalize()方法中将自己赋值给别的引用实现自救,此时他将被移除可回收对象的范围。如果此时对象没有自救,基本上就被回收了。
    二次标记时如果判断该对象已经调用过一次finalize()方法,则不会再调用,直接回被回收。

4.3、如何判断类是无用类

    方法区主要回收的是无用类,满足一下三点,则可判定一个类是无用类:
  • 该类的所有实例均已被回收,也就说堆内存中不存在该类的任何实例。
  • 加载该类的ClassLoad已经被回收。
  • 该类的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射来访问该类的方法。

三、垃圾回收算法

1、标记-清除算法

1.1、概念

    该算法是最基本的垃圾回收算法,首先对所有对象进行标记,就是前边说的二次标记判断的对象是否已经死了,标记后在统一回收,释放空间其他算法都是基于该算法的思想,针对不足之处进行优化。

1.2、缺点:

  • 效率低:一个对象回收需要经过标记和清除两个阶段,而这两个阶段效率都不高,导致整体回收效率比较低。
  • 空间碎片较多:该回收算法导致产生的空间碎片比较多。当一个对象需要的空间无法得到连续分配的时候,会触发提前出发一次垃圾回收。

 

2、复制算法

2.1、概念

    该算法将内存分成两份,每次使用其中的一块,当一块空间不足时,将还存活的对象复制到另一块内存中,然后回收到该块内存。该方式比较高校,也不会出现内存碎片的问题。

2.2、缺点

    内存空间始终只使用了一般,对内存浪费比较大。
 

2.3、应用

    现在商业虚拟机都采用该种方式。IBM研究表明,98%的对象都是“朝生夕死”的,所以不需要按照1:1分配空间。而是将虚拟机分割成一大块内存和一小块内存,即8:1:1。
    该算法将内存划分为一块较大的Eden区和两块较小的Survivor区,每次使用Eden和一块Survivor区,当需要回收时,将Eden和Survivor中存活的对象复制到另外一块Survivor中。

3、标记整理算法

    标记过程同标记清楚算法一样,标记后不是直接对可回收的对象进行清理,二十让存活的对象向一段移动,然后在清楚掉端边界以外的内存。

4、分代收集算法

    目前采用最多的回收算法。首先将内存空间分为新生代和老年代,也就是复制算法中的Eden和Survivor。需要分配内存时,首先在eden中分配内存,如果Eden中空间不足,则将Eden中存活的对象复制到Survivor中,并标记年龄+1,将年龄大的放到老年代中,虚拟机默认的最高年龄时15岁。然后根据各个年代的特点,采用适合各个年代的回收算法进行回收。
    在新生代中,每次回收都有大量的对象被回收,该处选用复制算法,每次付出很小的内存空间来存放回收时依然存活的对象。
    老年代中存放的一般都是长期存活的对象,回收释放的空间并不大。因此主要采用标记-清理和标记-整理算法来实现。

四、HotSpot 的算法实现

    HotSpot为了保证虚拟机的高效运行,对回收算法有着相当高的效率要求。

1、枚举根节点

    内存回收的区域一般空间很大,有的方法区也有几百兆,同事GC在收集的时候,所有线程都会停止,从大量的引用中查找可回收的也是一件很耗时的事情,如果真是这样,那系统会很糟糕。一般在类加载器加载完成后,会指定出那部分区域存储的是引用,垃圾回收的时候直接访问该区域就好了,大量节省了查找的时间。在HotSpot的实现中,采用一种OopMap的数据结构来实现。

2、安全点

    在OopMap的实现下,HotSpot可以很快的完成GC Roots枚举,但是引用关系可能变化,导致OopMap变化的指令很多,如果为每一条指令都创建OopMap,是很浪费空间的。因此HotSpot引入了一个安全点的概念,就是程序执行的时候并不是所有的时刻都可以停下来,而是要运行到安全点才能停下来,这个时候Gc才进行枚举操作。
    如何在GC发生时让所有线程都停止下来,具体有两种实现方式:
  • 抢先试中断:GC直接将线程中断,如果发现有线程不在安全点,就恢复让其运行到安全点,然后再中断。目前采用最多的一种中断机制。
  • 主动式中断:GC设置一个标志,所有线程运行时检查这个标志的状态,如果需要中断,则自己运行到安全区进行中断。

3、安全区域

    指在一段代码片段中,引用是不会发生改变的,在这个区域的任何地方都可以开始GC并中断线程。
    当线程进入安全区域是,会给自身添加一个标示,GC检查到该标识就可以进行回收。当线程要离开安全区域时,需要检查系统是否已经完成了GC或者完成了GC Roots可达性分析,如果没有完成就只能等到Gc完成后通知,才能离开安全区。

四、垃圾收集器

    如果说垃圾回收算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机对垃圾回收器没有任何规定,因此不通厂商对垃圾回收器的实现也不同。不同虚拟机提供的垃圾回收器不通,但是一般都会提供配置参数,供用户选择使用哪种垃圾回收器。用户可以根据自身系统的属性,结合各个年代的特点来选择搭配使用垃圾回收器。
    各个回收器的组合使用如下:

1、Serial收集器(串行收集器)(-XX:+UseSerialGC -XX:+UseSerialOldGC)

    最基本、历史最悠久的收集器。它是一个单线程的收集器,它只有一个线程去完成所有的垃圾收集工作,同时在它进行垃圾收集的时候,会停止调所有的引用线程,知道它收集完成。
    采用算法:新生代代用复制算法,老年代采用标记-整理算法。
    优点:没有线程交互的开销,相对于其他收集器简单高效

2、ParNew收集器

    ParNew收集器是Serial的多线程版本,在实现上与Serial公用的大量代码,很多参数配置也都是一样的。比如:-XX:SurivivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等、手机算法、对象分配规则、回收策略等也都与Serial一致。
    回收算法:新生代采用复制算法,老年代采用标记-整理算法。
    ParNew收集器的工作过程图:

    应用:主要应用于Server模式下的虚拟机首要选择,除了Serial收集器外,只有它可以与CMS收集器搭配使用。
    配置:

3、Parallel Scavenger收集器

    Parallel Scavenger收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程的收集器。
    Parallel Scavenger收集器关注点是打到一个可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
    停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能够提升用户体验,高的吞吐量可以提高CPU的利用率,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
    Parallel Scavenger收集器提供两个参数来控制吞吐量,主要如下:
  • -XX: MaxGCPauseMillis:最大垃圾收集停顿时间,GC回收尽量保证回收时间不超过阈值,如果设置的太小,容易引起频繁GC,反倒影响系统性能。
  • -XX: GCTimeRatio:设置吞吐量大小。垃圾回收时间占用总时间的比例。
  • -XX:+UseAdaptiveSizePolicy:开关参数,设置后,不需要手动设置各个区域空间的比例,虚拟机会根据系统自动调节。

4、Serial Old

    Serial Old是一个单线程老年代的收集器,采用标记整理算法。
    在Server模式下主要有两大用途:
  • 在1.5之前的版本中与Parallel Scavenger收集器搭配使用。
  • 作为CMS收集器的备选方案,在并发手机发生Concurrent Mode Failure时使用。

5、Parallel Old

    Parallel Old是Parallel Scavenger的老年代收集器,使用短线成和标记-整理算法,从JDK1.6开始使用。

 

6、CMS(Concurrent Mark Sweep)

    CMS(Concurrent Mark Sweep)是一种以获取最短停顿时间为目标的收集器。主要来满足大部分Java应用网站或者B/S架构的服务端上。是真正意义上的并发多线程收集器主要优点体现在:并发收集,低停顿。
    CMS收集器是采用标记-清除算法,回收主要分为四个阶段:
  • 初始标记:标记一下GC Roots可以关联到的对象。
  • 并发标记:GC Roots tracing过程,根可达算法。
  • 重新标记:修正并发标记时由于系统正在运行而发生变化的对象。这个阶段比初始标记耗时较长。
  • 并发清除:多线程并发清除被标记可回收的对象。

6.1、说明

    其中初始标记和重新标记同样需要应用线程停止,但是CMS回收器最耗时的并发标记和并发清除阶段,应用线程是不需要停止的,因此回收对整体性能影响较小。

6.2、缺点

  • CMS对CPU资源比较敏感,在回收阶段虽然不会停止应用线程,但是会占用一部分CPU资源,导致应用线程变慢,影响系统的整体吞吐量。CMS默认启动的线程数是(CPU数量+3)/4,这就意味着,当CPU数量大于4时,并发回收占用的CPU资源不少于25%,并且随着CPU数量的增加而下降,当CPU数量很少时,CMS收集器占用的CPU资源可能达到50%以上,这对系统时不小的额外开销。
  • CMS无法收集浮动垃圾,由于CMS在回收阶段,用户线程还在进行,就可能产生新的垃圾,这部分垃圾在标记阶段过后是未被标记的,CMS无法收集,这将导致Concurrent Mode failure 失败,导致系统触发Full GC。在JDK1.5中,CMS收集器当老年代使用了68%的空间就会激活CMS回收,如果老年代增长不是很快,可通过-XX:CMSInitiatingOccupancyFraction参数来设置触发CMS收集是空间占用的百分比,在JDK1.6中,该值已经默认调到了92%。要是CMS运行期间无法满足内存需求,则会触发Serial Old进行回收。
  • CMS是基于标记-清除算法实现的,为了解决内存碎片的问题,CMS提供了一个参数:-XX:UseCMSCompactAtFullCollection,改参数默认开启,标示CMS收集器在顶不住的时候会开启Full GC,这将导致停顿时间拉长。另一个参数:-XX:CMSFullGCsBeforeCompaction,这个参数用来设置执行多少次不压缩的Full GC后执行一次压缩的GC,默认是0。

6.3、CMS的相关参数

  • -XX:+UseConcMarkSweepGC:启用cms
  • -XX:ConcGCThreads:并发的GC线程数
  • -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  • -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
  • -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
  • -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-
  • -XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  • -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在remark阶段
 

7、G1收集器

7.1、G1收集器概念和细节详解

    G1收集器是面向服务端的一款高效的收集器,开发者在1.5版本开始进行开发,在1.7版本中进行了大量实验验证,知道1.8已经作为默认的GC收集器了。
    与其他收集器相比,G1具备如下特点:
  • 并行与并发:利用CPU多核的特点,在收集的时候让Java应用依然运行,来降低系统停顿的时间。
  • 分代收集:虽然不需要其他收集器配合,G1收集器可以根据不同的区域、采用不同的方式去处理新创建的和已经存活一段时间、熬过多次GC的旧对象,来获得更好的收集效果。
  • 空间整合:G1从整体看是基于标记-整理算法实现的,从局部region 来看是采用复制算法来实现的。因此在回收过程中不会产生内存碎片,这使得系统能够长期稳定的运行,在有大对象创建时,依然能够提供足够的内存空间。
  • 可预测的停顿:这是G1的一大优势。G1在追求停顿时间外,可以建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收上的时间不超过N毫秒。这几乎已经是实时java的垃圾回收器的特征了。
    在G1回收器中,已经不再是将内存区域划分为新生代、老年代了,而是划分成多个大小相等的region,JVM最多可以有2048个Region。一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以用参数"-XX:G1HeapRegionSize"手动指定Region大小,但是推荐默认的计算方式。虽然还保留了新生代和老年代的概念,但已经不是物理上隔离了,界限已经很模糊了。
    G1之所以建立可以测的停顿时间模型,因为它可以有计划的避免整个Java堆中进行全区域的垃圾收集。G1跟踪每个region区域的空间大小,在后台维护一张表,每次根据允许的回收时间,优先回收可释放空间最大的region。这种使用region划分内存空间以及优先级的区域回收,能有有效的保证G1回收器在有限的时间内可以获得尽可能高的回收效率。
    默认年轻代对堆内存的占比是5%,如果堆大小为4096M,那么年轻代占据200MB左右的内存,
对应大概是100个Region,可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统
运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%,可以
通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和Survivor对应的region也跟之前
一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个,s0对应100个,s1对应
100个。
    一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,也就是
说Region的区域功能可能会动态变化。
    G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样,唯一不同的是对大对象的处理,G1有专门分配大对象的Region叫Humongous区,而不是让大对象直接进入老年代的Region中。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按照上面算的,每Region是2M,只要一个大对象超过了1M,就会被放入Humongous中,而且一个大对象如果太大,可能会横跨多个Region来存放。
    Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开销。
    Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。
    G1收集器一次GC的运作过程大致分为以下几个步骤:
  • 初始标记(initial mark,STW):暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快 
  • 并发标记(Concurrent Marking):同CMS的并发标记
  • 最终标记(Remark,STW):同CMS的重新标记
  • 筛选回收(Cleanup,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序根据用户所期望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划,比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Region刚好需要200ms,那么就只会回收800个Region,尽量把GC导致的停顿时间控制在我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。不管是年轻代或是老年代,回收算法主要用的是复制算法,将一个region中的存活对象复制到另一个region中,这种不会像CMS那样回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。

7.2、G1垃圾收集分类

  • YoungGC:YoungGC并不是说现有的Eden区放满了就会马上触发,而且G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC
  • MixedGC:不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercen)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC
  • Full GC:停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。

7.3、G1回收器优化建议

    假设参数 -XX:MaxGCPauseMills 设置的值很大,导致系统运行很久,年轻代可能都占用了堆
内存的60%了,此时才触发年轻代gc。那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。或者是年轻代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代中。
    所以这里核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc。

7.4、每秒几十万的并发系统怎么优化

    就拿kafak来说,每秒处理几十万的数据很正常,一般内存也都在64G以上,分配给Eden区大概有30~40G左右,如果等到Eden达到阈值才进行回收,每次回收30~40G的空间,尽快Eden很快也需要几秒钟,这就导致每隔几分钟系统就要停顿几秒钟,这是无法接受的。
    可以设置G1收集器的-XX:MaxGCPauseMills参数来空时Eden区的回收时间间隔,比如可以设置50ms,假设50ms可以回收5G左右的空间,那么每次回收5G,系统卡顿50ms,用户是无感知的,这个是可以接受的。不要等到最后一起回收。
    G1比较适合解决大内存的机器JVM运行,可以很好的解决因为回收空间较大而导致回收时间拉长的问题。

7.5、如何选择GC回收器

  • 优先调整堆的大小让服务器自己来选择
  • 如果内存小于100M,使用串行收集器
  • 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
  • 如果允许停顿时间超过1秒,选择并行或者JVM自己选
  • 如果响应时间最重要,并且不能超过1秒,使用并发收集器

8、GC日志分析

8.1、GC回收代码示例

public class ApplicationCommons {
	private Object instance = null;
	// 创建该变量就是为了被回收的
	private byte[] bytes = new byte[1024 * 1024];
	public static void main(String[] args) {
		ApplicationCommons aA = new ApplicationCommons();
		ApplicationCommons aB = new ApplicationCommons();

		aA.instance = aB;
		aB.instance = aA;

		aA = null;
		aB = null;

		System.gc();
	}
}

8.2、GC原始日志和字段说明

Java HotSpot(TM) 64-Bit Server VM (25.161-b12) for windows-amd64 JRE (1.8.0_161-b12), built on Dec 19 2017 17:52:25 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8201664k(1072532k free), swap 17638848k(5077096k free)
CommandLine flags: -XX:InitialHeapSize=131226624 -XX:MaxHeapSize=2099625984 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
// minor gc回收
/**
* 1、0.583 本次GC开始的时间,也就是系统运行多少秒后执行的GC,也就是从JVM启动到本次GC的时间
* 2、[GC 和 [Full GC  本次gc的类型,如果有Full Gc说明本次GC停掉了所有线程(stop the word)。一般因为发生了分配担保失败。
* 3、PSYoungGen:gc发生的区域,ParallelScavenger 收集器PSYoungGen标示年轻代,PSYoungGen的名称跟GC收集器有关,不通GC收集器标示不同
* 4、6636K->1144K(37888K):GC前后的内存变化
* 5、中括号后边的6636K->1152K(123904K)标示:GC前后该区域的内存使用情况
* 6、0.0119551 secs:本次GC的耗时情况
* 7、[Times: user=0.02 sys=0.00, real=0.00 secs]:详细耗时,单位是毫秒。
		user:用户态消耗CPU时间
        sys:内核态消耗CPU时间
        real:操作从开始到结束经过的墙钟时间,墙钟时间包括各种非运算的等待耗时,比如磁盘IO、等待线程阻塞等。
        	CPU时间只是CPU运算的耗时。
*/
0.583: [GC (System.gc()) [PSYoungGen: 6636K->1144K(37888K)] 6636K->1152K(123904K), 0.0037508 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
0.586: [Full GC (System.gc()) [PSYoungGen: 1144K->0K(37888K)] [ParOldGen: 8K->1034K(86016K)] 1152K->1034K(123904K), [Metaspace: 3456K->3456K(1056768K)], 0.0120676 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
/**
* Full GC时各个区域回收前后的红箭使用情况。
*/
Heap
 PSYoungGen      total 37888K, used 328K [0x00000000d6400000, 0x00000000d8e00000, 0x0000000100000000)
  eden space 32768K, 1% used [0x00000000d6400000,0x00000000d6452030,0x00000000d8400000)
  from space 5120K, 0% used [0x00000000d8400000,0x00000000d8400000,0x00000000d8900000)
  to   space 5120K, 0% used [0x00000000d8900000,0x00000000d8900000,0x00000000d8e00000)
 ParOldGen       total 86016K, used 1034K [0x0000000082c00000, 0x0000000088000000, 0x00000000d6400000)
  object space 86016K, 1% used [0x0000000082c00000,0x0000000082d02a50,0x0000000088000000)
 Metaspace       used 3463K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值