概述

本文主要讲述垃圾收集算法和常用的几种垃圾收集器

垃圾收集算法

标记-清除算法(Mark-Sweep)

  • 算法分为 "标记" 和 "清除" 两阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象
  • 缺点
  • 效率问题, 标记和清理两个过程效率都不高
  • 空间问题, 标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发一次的垃圾收集动作。
  • 效率不高,需要扫描所有的对象,堆越大,GC 越慢
  • 存在内存碎片问题。GC 次数越多,碎片越严重

标记-整理算法 (Mark-Compact)

  • 标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界意外的内存。
  • 没有内存碎片
  • 对 Mark-Sweep 耗费更多的时间进行 compact

标记-复制算法 (Copying)

  • 将可用内存划分为2块,每次只使用其中的一块,当半区内存用完了,仅将还存活的独享复制到另外一块上,然后就把原来整块内存空间一次性清理掉
  • 这样使得每次内存对是对真个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要一动堆顶指针,按循序分配就可以了,实现简单,运行效率高。 只是这种算法的代价是将内存缩小为原来的一半。代价高昂。
  • 现在的商业虚拟机中都是采用这种算法来回收新生代
  • 将内存分一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor(存活者) 空间,当回收时将eden和survivor还存活的对象一次性拷贝 到另外一个块survivor 空间上,然后清理掉eden和用过的survivor
  • Oracle Hotspot 虚拟机默认eden 和 survivor 的大小比例是8:1 也就是每次只有10%的内存是"浪费"的。
  • 复制收集算法在独享存活率高的时候,效率有所下降
  • 如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
  • 只需要扫描存活对象,效率更高。
  • 不会产生碎片
  • 需要浪费额外的内存作为复制区
  • 复制算法非常适合生命周期比较短的对象,因为每次GC总能回收大部分的对象,复制的开销比较小。
  • 根据IBM的专门研究, 98%的java 对象只会存活1个GC周期,对这些对象很适合复制算法。而且不用1:1 划分工作区和复制区的空间。

分代算法 (Generational)

  • 当前商业虚拟机的垃圾收集都是采用"分代收集" (Generational Collecting)算法,根据独享不同的存活周期将内存划分为几块
  • 一般是把Java堆分作新生代和老年代, 这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次GC都有大批量对象死去,只有烧流量存活, 那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。
  • 综合前面几种GC算法的优缺点,针对不同生命周期的对象采用不同的GC算法
  • 新生代采用 Copying
  • 老年代采用 Mark-Sweep or Mark-Compact
  • Hotspot JVM 6中共分为三个代: 年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation).
  • 年轻代(Young Generation)
  • 新生成的对象都放在新生代。年轻代用复制算法记性GC(理论上,年轻代对象生命周期非常短,所以适合复制算法)
  • 年轻代分三个区。一个Eden 区, 两个Survivor区(可以通过参数设置Survivor个数)。对象在Eden区中生成。 当Eden区满时, 还存活的对象 将被复制到另外一个Survivor区, 当第一个Survivor区也满的时候,从第一个Survivor区复制过来的并且此时还存活的独享,将被复制到老年代。2个Survivor 的完全对称,轮流替换。
  • Eden 和2个Survivor的缺省比例是8:1:1, 也就是10%的空间将被浪费,可以根据GC log的信息调整大小的比例。
  • 老年代(Old Generation)
  • 存放了经过一次或多次GC还存活的对象
  • 一般采用 Mark-Sweep或 Mark-Compact 算法进行GC
  • 有很多垃圾收集器可以选择。每种垃圾收集器可以看做是一个GC算法的具体实现。可以更具具体应用的需求选用合适的垃圾收集器(追求吞吐量?追求最短的响应时间)。
  • 永久代
  • 并不属于堆 (Heap) 但是GC也会涉及到这个区域
  • 存放了每个Class的结构信息, 包括常量池、字段描述、方法描述。与垃圾收集要收集的Java对象关系不大。
  • 内存结构

JVM 垃圾收集算法和垃圾收集器_JVM

垃圾收集器

如果说手机算法是内存回收的方法理论,那么垃圾收集器就是内存回收的具体实现。

虽然我们对各收集器进行比较,但并非为了挑选出一个最好的收集器,因为直到现在为止还没有最好的垃圾收集器出现, 更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的收集器,试想一下:如果有一个完美无暇的垃圾收集器适用于所有场景,那么我们 Java 虚拟机就不会去实现那么多的垃圾收集器了。

查询当前使用的 JVM 信息查询命令 ​​java -XX:+PrintCommandLineFlags -version​

➜  ~ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)

Serial 收集器

  • 单线程收集器,收集时会暂停所有工作线程(Stop The World, 简称STW),使用复制收集算法,虚拟机运行在 Client 模式的默认新生代收集器
  • 最早的收集器,单线程进行GC
  • New 和 Old Generation 都可以使用
  • 在新生代, 采用复制算法; 在老年代,采用Mark-Compact算法
  • 因为使用单线程GC,没有多线程切换的额外开销,简单实用。
  • Hotspot Client 模式缺省的收集器
  • Safepoint 安全点
  • JVM 参数:​​-XX:+UseSerialGC -XX:+UseSerialOldGC​

JVM 垃圾收集算法和垃圾收集器_JVM_02

PerNew 收集器

  • ParNew 收集器就是 Serial 的多线程版本,除了使用多个收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial 收集器一模一样
  • 对应的这种收集器是虚拟机运行在 Server 模式的默认新生代收集器,在单 CPU 的环境下,ParNew 收集器的效果并不会比Serial收集器有更好的效果
  • Serial 收集器的在新生代的多线程版本
  • 使用复制算法(因为针对新生代)
  • 只有在多CPU的环境下,效率才会比Serial收集器高
  • 可以通过-XX:ParallelGCThreads来控制GC线程数的多少。需要结合具CPU的个数
  • Server模式下新生代的缺省收集器。
  • JVM 参数:​​-XX:UseParNewGC​

JVM 垃圾收集算法和垃圾收集器_JVM_03

Parallel Scavenge 收集器(1.8 默认)

  • Parallel Scavenge 收集器也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与收集策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长的STW换取总吞吐量最大化。
  • JDK 1.8 默认垃圾收集器
  • JVM 参数:`-XX:UseParallelGC(年轻代) -XX:UseParallelOldGC(老年代)

Serial Old 收集器

  • Serial Old收集器是单线程收集器,使用标记-整理算法,是老年代的收集器

JVM 垃圾收集算法和垃圾收集器_垃圾收集器_04

Parallel Old 收集器

  • 老年代版本吞吐量优先的收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择, 因为PS无法与CMS收集器配合工作。
  • Parallel Scavenge 在老年代的实现
  • 在JVM 1.6 才出现Parallel Old
  • 采用多线程,Mark-Compact 算法
  • 更注重吞吐量
  • Parallel Scavenge + Parallel Old = 高吞吐量,但是GC停顿可能不理想

CMS (Concurrent Mark Sweep) 收集器

  • CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间, CMS收集器使用的是标记-清除算法

JVM 垃圾收集算法和垃圾收集器_垃圾收集器_05

  • 特点
  • 最求最短停顿时间,非常适合Web应用
  • 针对老年代,一般结合ParNew使用
  • Concurrent, GC 线程和用户线程并发工作(尽量并发)
  • Mark-Sweep
  • 只有在多CPU环境下才有意义
  • 使用-XX:+UseConcMarkSweepGC启用
  • CMS收集器缺点
  • CMS以牺牲CPU资源的代价来减少用户线程的停顿。当CU个数小于4的时候,可能对吞吐量影响非常大
  • CMS在并发清理的过程中,用户线程还在跑,这时候需要预留一部分空间给用户线程。
  • CMS用Mark-Sweep, 会带来碎片问题。碎片过多的时候会容易平凡触发Full GC

CMS 收集器执行步骤

  • CMS 收集器执行步骤总览
  • Phase 1: Initial Mark
  • Phase 2: Concurrent Mark
  • Phase 3: Concurrent Preclean
  • Phase 4: Concurrent Abortable Preclean
  • Phase 5: Final Remark
  • Phase 6: Concurrent Sweep
  • Phase 7: Concurrent Reset
  • Phase 1: Initial Mark (初始标记)
  • 这个是CMS两次 stop-the-world 事件的其中一次,这个阶段的目标是:标记哪些直接被GC Root引用或者被年轻代存活对象所引用的所有对象

JVM 垃圾收集算法和垃圾收集器_垃圾收集器_06

  • Phase 2: Concurrent Mark (并发标记)
  • 在这个阶段 Garbage Collector 会遍历老年代,然后标记所有存活的对象,它会更具上个阶段找到的GC Roots 遍历查找。并发标记阶段, 他会与用户的应用程序并发运行。 并不是老年代所有的存活对象都会被标记, 因为在标记期间用户的程序可能会改变一些引用

JVM 垃圾收集算法和垃圾收集器_sed_07

  • 在上面的图中,与阶段1的图进行对比就会发现有一个对象的引用已经发生额变化。
  • Phase 3: Concurrent Preclean (并发预先清理)
  • 这个也是一个并发阶段,与应用的线程并发运行,并不会 stop 应用的线程。在并发运行的过程中,一些对象可能会发生变化,但是这种情况发生时。JVM 将会包含这个对象的区域(Card)标记为 Dirty, 这也就是 Card Marking.
  • 在 pre-clean 阶段,哪些能够从 Dirty 对象到达的对象也会被标记,这个标记做完后,dirty card 标记就会被清除了。
  • Phase 4: Concurrent Abortable Preclean (并发预先可能失败的清理)
  • 这也是一个并发阶段,但是同样不会影响用户的应用线程,这个阶段是为了尽量承担 STW (stop-the-world)中最终标记阶段的工作。这个阶段持续时间依赖于很多的因素,由于这个阶段是在重复做很多相同的工作,直接满足一些条件(比如:重复迭代的次数、完成的工作量或者时钟时间等)。
  • Phase 5: Final Remark (最终重新标记)
  • 这个阶段是第二个STW阶段,也是 CMS的最后一个,这个阶段的目标是标记老年代所有的存活对象,由于之前的阶段是并发执行的,GC线程可能跟不上应用程序的变化,为了完成标记老年代所有存活对象的目标,STW就非常有必要了
  • 通常CMS的Final Remark 阶段会在年轻代尽可能干净的时候运行,目的是为了减少STW发生的可能性(年轻代存回对象过多的话,也会导致老年代设计的存活对象会很多)。这个阶段会比前面几个阶段更复杂一些。
  • 标记阶段完成,经历过这5个阶段之后,老年代所有存活的对象都被标记过了,现在可以通过清除算法去清理哪些老年代不再使用的对象。
  • 这个阶段的时间一般比初始标记的时间稍长,远远比并发标记的时间短,主要用到三色标记里面的增量更新算法
  • Phase 6: Concurrent Sweep (并发清理)
  • 这里不需要STW,它是与用户的应用程序并发运行的,这个阶段是:清除哪些不再使用的对象,回收他们占用的空间为将来使用。
  • Phase 7: Concurrent Reset (并发重置)
  • 这个阶段是并发执行的,它会重设 CMS 内部的数据结构,为下次的GC做准备。

CMS 的相关核心参数

  1. -XX:+UseConcmarkSweepGC: 启用 cms。
  2. -XX:ConcGCThreads: 并发的 GC 线程数。
  3. -XX:+UseCMSCompactAFFullCollection: Full GC 之后做压缩整理(减少碎片)。
  4. -XX:CMSFullGCsBeforeCompaction: 多少次 Full GC 之后压缩一次,默认是0,代表每次 Full GC 后都会压缩一次。
  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发 Full GC (默认是92,这是百分比)。
  6. -XX:+UsecmslnitiatingOccupancyOnly: 只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整。
  7. -XX:+CMSScavengeBeforeRemark: 在 CMS GC前启动一次 minor gc,目的在于减少老年代对年轻代的引用,降低 CMS GCI的标记阶段时的开销般 CMS 的GC耗时 80% 都在标记阶段。
  8. -XX:+CMSParallellnitialMarkEnabled: 表示在初始标记的时候多线程执行, 缩短 STW。
  9. -XX:+CMSParallelRemarkEnabled: 在重新标记的时候多线程执行, 缩短 STW。

垃圾回收算法

三色标标记算法

提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性。

JVM 垃圾收集算法和垃圾收集器_垃圾收集器_08

我们将对象分为三种类型:

  • 黑色:根对象,或者该对象与它的子对象都被扫描过(对象被标记了,且它的所有field也被标记完了)
  • 灰色:对象本身被扫描,还没有扫描完该对象中的子对象(它的field还没有被标记或标记完)
  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象(对象没有被标记到)

三色标记过程

  • 第一步,三色标记算法,如果将根对象设置为黑色,那么下级节点的为灰色,再下面的的为白色
  • 第二步,灰色扫描完毕后,那么剩下的白色变为灰色
  • 第三步,灰色扫描完毕后,那么全部被标记为黑色,不可达的还是为白色

三色标记算法的对象丢失

  • 但是如果在标记过程中,应用程序也进行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失
  • 例子:
  • 第一步,初始 Root(黑)-> A(黑) Root(黑)-> B(灰)-> C(白)
  • 第二步,在当前场景下执行如下操作
  • Root(黑)-> A(黑)-> C(白) Root(黑)-> B(黑)
  • A.c = C
  • B.c = null
  • 第三步,如果内存回收的时候,就会将 C 回收掉,会导致 C 对象丢失。

SATB(Snapshot-At-The-Beginning)

  • SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图
  • 在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象也认为是垃圾对象。
  • 如何找到在GC过程分配的对象呢?每个region记录着两个top-at-mark-start(TAMS) 指针,分别为prevTAMS 和nextTAMS。在TAMS以上的独享就是新分配的,因而被视为隐式marked
  • 通过这种方式我们就找到了再GC过程中新分配的对象,并把这些对象认为是活的对象。
  • 解决了对象在GC过程中分配的问题,呢么GC过程中引用繁盛变化的问题是怎么解决的呢?
  • G1给出的解决办法是通过Write Barrier. Write Barrier 就是堆引用字段进行赋值做了额外处理。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化
  • mark 的过程就是遍历heap标记live object的过程,采用的三色标记算法,这三种颜色为white(表示还未访问到)、gray(访问到但是它用到的引用还诶有完全扫描)、black (访问到而且其用到的引用完全扫描完)
  • 整个三色标记算法就是从GC Roots出发遍历heap,针对可达独享先标记white为gray, 然后再标记gray为black; 遍历完成之后所有可达对象都是black的,所有 white 都是可以回收的。
  • SATB仅仅对于在marking开始阶段进行"snapshot"(marked all reachable at mark start), 但是concurrent 的时候并发修改可能造成对象漏标记

漏标的场景

  • 对black 新引用了一个white对象,然后从gray对象中删除了对该white 对象的引用,这样会造成该white 对象漏标记。
  • 对black 新引用了一个white对象,然后从gray对象删除了一个引用该white对象的white对象,这样也会造成该white对象漏标记。
  • 对black 新引用了一个刚new出来的一个white对象,没有其他的gray对象引用该white对象,这样也会造成该white对象漏标记。
  • 对于三色算法在concurrent的时候可能产生漏标的问题,SATB在marking阶段中,对于从gray对象移除的目标引用对象标记为gray, 对于从gray对象移除的 目标引用对象标记为gray,对于black引用的新产生的对象标记为black; 由于是在开始的时候进行snapshot, 因而可能存在Floating Garbage

漏标和误标

  • 误标识没有关系,顶多造成浮动垃圾,在下次GC还是可以回收的,但是漏标的后果是致命的把本应该存活的对象给回收了,从而影响程序的正确性。
  • 漏标的情况只会发生在白色对象中,且满足一下任意一个条件
  • 并发标记时,应用线程给一个黑色独享的引用类型字段赋值了该白色对象
  • 并发标记时,应用线程删除所有灰色对象到该白色对象的引用
  • 第一种情况,利用post-write barrier 记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍
  • 对于第二种情况,利用pre-write barrier, 将所有即将被删除的引用关系的旧引用记录下来,最后这些旧引用为根重新扫描一遍

垃圾收集器参数

参数

描述

UseSerialGC

虚拟机运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old 的收集器组合进行内存回收

UseParNewGC

打开此开关后,使用PerNew + Serial Old的收集器组合进行内存回收

UseConcMarkSweepGC

打开此快关后,使用PerNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器作为CMS收集器处理

UseParallelGC

失败后的后备收集器使用

UseParalleOldGC

虚拟机运行的Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old (PS MarkSweep) 的收集器组合进行内存回收

SurvivorRatio

新生代中Eden区域与Surivivor 区域的容量比值, 默认为8, 代表 Eden: Surivivor = 8:1

PretenureSizeThreshold

直接晋升到老年代的对象大小, 设置这个参数后,大于这个参数的对象将直接在老年代分配

MaxTenuringThreshold

晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就增加1,当超过这个参数值就进入老年代

UseAdaptiveSizePolicy

动态调整Java堆中各个区域的大小以及进入老年代的年龄

HandlePromotionFailure

是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况

ParallelGCThreads

设置并行GC时惊醒内存回收的线程数

GCTimeRatio

GC时间占总时间的比率,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge收集器时生效

MaxGCPauseMillis

设置GC的最大停顿时间。仅在使用Parallel Scavenge 收集器时生效

CMSInitialingOccupancyFraction

设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68% ,仅在使用CMS收集器时生效

UseCMSCompactionAtFullCollection

设置CMS收集器在完成垃圾回收后是否需要进行一次内存碎片整理,仅在使用CMS收集器时生效

CMSFullGCsBeforeCompaction

设置CMS后机器在进行若干次垃圾收集器后再启动一次内存碎片整理仅在使用CMS收集器时生效

GC 日志分析

ParallelGC 收集器 GC 日志分析

GC 日志

  • 代码案例
public class MyTest1 {

public static void main(String[] args) {
int size = 1024 * 1024;
byte[] myAlloc1 = new byte[2 * size];
byte[] myAlloc2 = new byte[2 * size];
byte[] myAlloc3 = new byte[3 * size];
//byte[] myAlloc4 = new byte[2 * size];

System.out.println("hello world");
}
}
  • 运行时JVM参数
-verbose:gc  //报告每个垃圾收集事件,输出虚拟机中垃圾回收的详细日志
-Xms20M //初始化堆空间大小
-Xmx20M //最大堆空间大小,-Xms和-Xmx设置成一样可以避免垃圾回收造成的抖动问题
-Xmn10M //新生代内存大小
-XX:+PrintGCDetails //打印GC回收详细日志
-XX:SurvivorRatio=8 //新生代中Eden和Survivior的比例默认为8那么 Eden(对象发源地):Form Survivor:To Survivor = 8:1:1
  • GC日志
[GC (Allocation Failure) [PSYoungGen: 6195K->624K(9216K)] 6195K->4728K(19456K), 0.0083276 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
hello world
Heap
PSYoungGen total 9216K, used 4019K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 41% used [0x00000007bf600000,0x00000007bf950dd8,0x00000007bfe00000)
from space 1024K, 60% used [0x00000007bfe00000,0x00000007bfe9c010,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
Metaspace used 3067K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K
  • GC日志解析
  • GC 标记表示触发了一次GC回收, 前面没有Full修饰,标明这是一个Minor GC,注意它不代表只是GC新生代,并且现有的不管是新生代还是老年代都会STW
  • Allocation Failure 表示年轻代没有足够的内存能够存储新的数据
  • PSYoungGen 表示本次GC发生在年轻代使用的是Parallel Scavenge 收集器
  • 6195K->624K(9216K) 表示新生代 6195 表示当前新生代已使用容量, 624 表示GC后的使用容量, 9216 表示该区域的总容量,单位:KB
  • 6195K->4728K(19456K) 表示总的堆内存 三个参数分别表示: 堆回收之前大小,堆回收后的大小,堆的总大小
  • 0.0083276 secs 表示GC耗时,单位是秒
  • [Times: user=0.01 sys=0.00, real=0.01 secs] 分别表示用户耗时,内核耗时,总耗时
  • PSYoungGen total 9216K, used 4019K 表示新生代总内存 9216K, 已使用 4019K
  • eden space 8192K, 41% used , eden 区内存大小 8192K,已使用 41%
  • from space 1024K, 60% used , from Survivor 区内存大小 1024K,已使用 60%
  • to space 1024K, 0% used , eden Survivor 区内存大小 1024K,已使用 0%
  • ParOldGen total 10240K, used 4104K 老年代的总内存大小10240K, 已使用 4104K
  • 新生代回收内存:6195-624 = 5571k //执行完gc后,新生代执行垃圾回收后堆空间回收内存
  • 实际的回收内存:6195-4728 = 1467k //执行完gc后,总的堆空间释放的容量
  • 年老代使用内存:5571-1467 = 4104k (新生代的释放内存:会分为1.新生代->老年代,2.真实被回收)

Full GC

GC 日志

[GC (Allocation Failure) [PSYoungGen: 6195K->614K(9216K)] 6195K->4710K(19456K), 0.0127704 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 6999K->6999K(9216K)] 11095K->15199K(19456K), 0.0106521 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 6999K->2489K(9216K)] [ParOldGen: 8200K->8192K(10240K)] 15199K->10681K(19456K), [Metaspace: 3070K->3070K(1056768K)], 0.0075955 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
hello world
Heap
PSYoungGen total 9216K, used 4977K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 60% used [0x00000007bf600000,0x00000007bfadc7a8,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 80% used [0x00000007bec00000,0x00000007bf4000d8,0x00000007bf600000)
Metaspace used 3150K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 346K, capacity 388K, committed 512K, reserved 1048576K
  • Full GC 一般是年老代触发的GC
  • Ergonomics 是以种标识,
  • PSYoungGen: 6933K->3507K(9216K) ,采用Parallel Scavenge 新生代垃圾收集器
  • ParOldGen: 7176K->7168K(10240K) ,采用Parallel Old 老年代垃圾收集器 ,老年代的对象增多,是由于部分来自新生代
  • Metaspace: 元空间

注意

  • 在新生代内存不足的时候,但是遇到了大对象会直接存储到老年代中。

CMS 收集器 GC 日志分析

JVM 参数设置

-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:+UseConcMarkSweepGC //设置使用CMS回收器

CMS 收集器的 GC 日志

[GC (CMS Initial Mark) [1 CMS-initial-mark: 8194K(10240K)] 12935K(19456K), 0.0004987 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Final Remark) [YG occupancy: 4741 K (9216 K)][Rescan (parallel) , 0.0010423 secs][weak refs processing, 0.0000103 secs][class unloading, 0.0003310 secs][scrub symbol table, 0.0006947 secs][scrub string table, 0.0001765 secs][1 CMS-remark: 8194K(10240K)] 12935K(19456K), 0.0023344 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]