【Java 虚拟机笔记】垃圾收集器相关整理
2019-02-25 本文已影响50人
58bc06151329
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. 概述
- Java 中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。如果不进行垃圾回收,内存迟早都会被消耗空,因为在不断的分配内存空间而不进行回收。除非内存无限大,可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。
- 垃圾回收能自动释放内存空间,减轻编程的负担,虚拟机的一个系统级线程会自动释放该内存块。
- 垃圾回收意味着程序不再需要的对象是 " 无用信息 ",这些信息将被丢弃。
- 当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。
- 事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。
- 由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。
- 碎片是分配给对象的内存块之间的空闲内存洞。
- 碎片整理将所占用的堆内存移到堆的一端,虚拟机将整理出的内存分配给新的对象。
- Java 虚拟机规范没有明确地说明虚拟机使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做 2 件基本的事情。
- 发现无用信息对象。
- 回收被无用对象占用的内存空间,使该空间可被程序再次使用。
2. 垃圾收集算法
- GC 是垃圾收集的意思(Gabage Collection),Java 提供的 GC 功能可以自动也只能自动地回收堆内存中不再使用的对象,释放资源(目的),Java 语言的显式操作方法
gc()
只是通知,不是立即执行。- 对于 GC 而言,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。
- 垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,当一个对象不再被引用的时候,按照特定的垃圾收集算法来实现资源自动回收的功能。
2.1 引用计数法
- 给对象添加引用计数器,当引用对象时计数器 + 1,引用失效时,计数器 - 1,当计数器等于 0 时,对象失效,内存可以被回收。
- 优点在于实现简单高效,缺点是对象之间的互相循环引用问题不好解决,并且开销较大,频繁且大量的引用变化,带来大量的额外运算。
- 微软的 COM(Component Object Model)技术、使用 ActionScript 3 的 FlashPlayer、Python 语言使用了该算法进行内存管理。

2.2 根搜索法/可达性分析算法
- 通过 GC Roots 可达的对象路径称为引用链(reference chain),当一个对象没有引用链时(即从 GC Roots 不可达)则视为不可用对象,内存可以被回收。
- 优点在于更加精确和严谨,可以分析出循环数据结构相互引用的情况,缺点是实现比较复杂,需要分析大量数据,消耗大量时间,分析过程需要 GC 停顿(引用关系不能发生变化),即停顿所有 Java 执行线程(Stop The World)。
- Java、C# 使用该算法进行垃圾收集。
- Java 中,可作为 GC Roots 的对象包括有。
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI(Native 方法)引用的对象。

Java 中可以主动垃圾回收吗
- 每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过
getRuntime()
方法获取当前运行。java.lang.System.gc()
只是java.lang.Runtime.getRuntime().gc()
的简写,两者的行为没有任何不同。- 这两个命令只是建议虚拟机安排 GC 运行,有可能完全被拒绝。
- GC 本身是会周期性的自动运行的,由虚拟机决定运行的时机,而且现在的版本有多种更智能的模式可以选择,还会根据运行的机器自动去做选择,就算真的有性能上的需求,也应该去对 GC 的运行机制进行微调,而不是通过使用这个命令来实现性能的优化。
判断对象生存还是死亡
- 要真正宣告一个对象死亡,至少要经历 两次 标记过程。
- 在可达性分析后发现到 GC Roots 没有任何引用链相连时,被 第一次标记,进行一次筛选,筛选条件是 此对象是否有必要执行
finalize()
方法。 - 虚拟机定义为 没有必要 执行的情况有两种。
- 对象没有覆盖
finalize()
方法。 -
finalize()
方法已经被虚拟机调用过。 - 这两种情况就可以认为对象已死,可以回收。
- 对象没有覆盖
- 虚拟机定义为 有必要 执行的情况。
- 将有必要执行
finalize()
方法的对象,放入 F-Queue 队列中。 - 稍后在虚拟机自动建立、低优先级的 Finalizer 线程(可能多个线程)中触发这个方法,但并不承诺会等待它运行结束。
- 将有必要执行
- GC 将对 F-Queue 队列中的对象进行 第二次小规模标记。
- 如果对象在其
finalize()
方法中重新与引用链上任何一个对象建立关联,第二次标记时会将其移出 " 即将回收 " 的集合。 - 如果对象没有,也可以认为对象已死,可以回收。
- 如果对象在其
- 在可达性分析后发现到 GC Roots 没有任何引用链相连时,被 第一次标记,进行一次筛选,筛选条件是 此对象是否有必要执行
- 一个对象的
finalize()
方法只会被系统自动调用一次,经过finalize()
方法逃脱死亡的对象,第二次不会再调用。
2.3 Java 中的引用类型
- 由强到弱分为强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference)四种。
-
强引用 就是指在程序代码中普遍存在的,类似
Object obj = new Object()
这类的引用,只要强引用还存在,垃圾收集器 永远 不会回收被引用的对象。- 强引用具备的三个特点。
- 强引用可以直接访问目标对象。
- 强引用所指向的对象在任何时候都不会被系统回收。Java 虚拟机宁愿抛出 OOM 异常也不回收强引用所指向的对象。
- 强引用可能导致内存泄露。
- 强引用具备的三个特点。
-
软引用 是用来描述一些还有用但并非必须的对象。
- 对于软引用关联着的对象,在系统 将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- 在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。
- 可以使用命令行选项 -XX:SoftRefLRUPolicyMSPerMB = <N> 来控制清除速率。
-
弱引用 是用来描述非必须的对象。
- 强度比软引用更弱一些,被弱引用关联的对象 只能生存到下一次垃圾收集发送之前。
- 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
- 一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册引用队列中。
- 在 JDK 1.2 之后,提供了 WeakReference 类来实现弱引用。
- 软引用、弱引用都非常适合来保存那些可有可无的缓存数据。当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
-
虚引用 也称为幽灵引用或者幻影引用,是最弱的一种引用关系。
- 一个持有虚引用的对象,和没有引用几乎是一样的,随时都有可能被垃圾回收器回收。
- 当试图通过虚引用的
get()
方法取得强引用时,总是会失败。 - 虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
- 在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用(在堆外内存回收中有被使用)。
-
强引用 就是指在程序代码中普遍存在的,类似
3. 垃圾收集算法
3.1 标记-清除(Mark-Sweep)算法
- 标记-清除算法是最基础的收集算法,算法名字表明这个算法的垃圾收集过程包括两步:标记和清除。
- 判定垃圾的过程就是标记过程,在标记过后的清除过程中会清理标记为垃圾的对象。
- 算法有两个不足。
- 效率 的角度而言,标记和清除的效率不高。
- 空间 的角度而言,标记清除后会产生大量不连续的内存碎片,空间碎片太多的话可能导致以后分配大块内存时失败的问题,这样就会触发另一次垃圾收集操作。

3.2 复制(Copying)算法
- 复制算法是为了解决标记-清除算法效率不高的问题的,它将可用内存按照容量分为大小相等的两部分,每次只使用其中的一块。当一块的内存用完了,就将还存活的对象复制到另一块,然后再把已经使用过的内存空间一次性清理掉。这样使得每次是对整个半区进行内存回收,内存分配时也不需要考虑内存碎片的问题,只要移动堆顶指针,按顺序进行分配就好。

- 不过这个算法使得内存只有一半能用,代价太高。
- 现在的虚拟机都采用这种方法来回收 新生代,不过不是 1:1 分配,而是将堆内存分为一块较大的 Eden 空间和 两块较小的 Survivor 空间,每次使用 Eden 和一个 Survivor 空间。
- 当回收时,将 Eden 和 Survivor 中还存活的对象复制到另一块 Survivor 中,然后清理 Eden 和使用过的 Survivor 空间。
- HotSpot 虚拟机默认的 Eden 和 Survivor 比例是 8:1,即 Eden 占堆的 80% 空间,Survivor 占 10% 的空间,每次只能使用 90% 的堆空间。
- 不过并不能保证每次回收只有不多于 10% 的对象存活,当 Survivor 空间不够时,需要使用其他内存空间(老年代)进行 分配担保(Handle Promotion),即如果 Survivor 空间不够,存活的对象直接进入老年代。
3.3 标记-整理(Mark-Compact)算法
- 复制收集算法在对象存活率较高时就需要进行较多的复制操作,效率就会降低。更关键的是,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都存活的极端情况,所以在老年代中一般不使用这种算法。
- 根据 老年代 的特点,使用另一种标记-整理算法,标记过程和标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是整理存活的对象,将存活的对象都向一端移动,然后直接清理掉边界外的内存。

3.4 分代收集算法
- 现在的虚拟机都使用 " 分代收集 " 算法,这种算法只是根据对象的存活周期的不同将内存划分为几块。一般把 Java 堆空间分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。
- 在新生代,每次垃圾收集都会有大量的对象死去,只有少量存活,这样就可以选择复制算法,只需复制少量存活的对象就可以完成垃圾收集。
- 在老年代中,对象的存活率高、没有额外的空间对它进行分配担保,就必须采用标记-清除或标记-整理算法来进行回收。

4. HotSpot 虚拟机
4.1 HotSpot 虚拟机中对象可达性分析的实现
可达性分析的问题
- 从可达性分析知道,GC Roots 主要在全局性的引用(常量或静态属性)和执行上下文中(栈帧中的本地变量表),要在这些大量的数据中,逐个检查引用,会消耗很多时间。
- 可达性分析期间需要保证整个执行系统的一致性,对象的引用关系不能发生变化,这里会导致 GC 进行时必须停顿所有 Java 执行线程(称为 " Stop The World ",是虚拟机在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉。几乎不会发生停顿的 CMS 收集器中,枚举根节点时也是必须要停顿的)。
枚举根节点
- 枚举根节点也就是查找 GC Roots。目前主流虚拟机都是准确式 GC,可以直接得知哪些地方存放着对象引用,所以执行系统停顿下来后,并不需要全部、逐个检查完全局性的和执行上下文中的引用位置。
- 在 HotSpot 中,使用一组称为 OopMap 的数据结构来达到这个目的。
- 在类加载时,计算对象内什么偏移量上是什么类型的数据。
- 在 JIT 编译时,也会记录栈和寄存器中的哪些位置是引用。
- 这样 GC 扫描时就可以直接得知这些信息。
- 在 HotSpot 中,使用一组称为 OopMap 的数据结构来达到这个目的。
安全点
- HotSpot 在 OopMap 的帮助下可以快速且准确的完成 GC Roots 枚举。
- 但是运行中,非常多的指令都会导致引用关系变化,如果为这些指令都生成对应的 OopMap,需要的空间成本太高。
- 因此只在特定的位置记录 OopMap 引用关系,这些位置称为 安全点(Safepoint),即程序执行时并非所有地方都能停顿下来开始 GC。
- 安全点的选定不能太少,否则 GC 等待时间太长。也不能太多,否则 GC 过于频繁,增大运行时负荷。
- 所以,基本上是以程序 " 是否具有让程序长时间执行的特征 " 为标准选定。
- " 长时间执行 " 最明显的特征就是指令序列复用,如:方法调用、循环跳转、循环的末尾、异常跳转等。
- 只有具有这些功能的指令才会产生 Safepoint。
如何在安全点上停顿
- 对于 Safepoint,如何在 GC 发生时让所有线程(不包括 JNI 线程)运行到其所在最近的 Safepoint 上再停顿下来,主要有两种方案可选。
- 抢先式中断(Preemptive Suspension),不需要线程主动配合,在 GC 发生时,首先中断所有线程,如果发现不在 Safepoint 上的线程,就恢复让其运行到 Safepoint 上。现在几乎没有虚拟机实现采用这种方式。
-
主动式中断(Voluntary Suspension),在 GC 发生时,不直接操作线程中断,而是仅简单设置一个标志,让各线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。
- 轮询标志的地方和 Safepoint 重合。
安全区域
- 程序不执行时没有 CPU 时间(Sleep 或 Blocked 状态),无法运行到 Safepoint 上再中断挂起,这就需要安全区域来解决。
- 一段代码片段中,引用关系不会发生变化,在这个区域中的任意地方开始 GC 都是安全的。
- 线程执行进入 Safe Region,首先标识自己已经进入 Safe Region,线程被唤醒离开 Safe Region 时,其需要检查系统是否已经完成根节点枚举(或整个 GC),如果已经完成,就继续执行, 否则必须等待,直到收到可以安全离开 Safe Region 的信号通知。
4.2 HotSpot 虚拟机中的垃圾收集器
4.2.1 Serial 收集器
- Serial 收集器是最古老,最稳定以及效率高 的收集器,可能会产生较长的停顿,只使用一个线程去回收。
- 新生代、老年代使用串行回收,新生代复制算法、老年代标记-压缩。
- 垃圾收集的过程中会 Stop The World(服务暂停)。
- 使用方法:-XX:+UseSerialGC 可设定串联收集,在 JDK Client 模式,不指定虚拟机参数,默认是串行垃圾回收器。

4.2.2 ParNew 收集器
- ParNew 收集器其实是 Serial 收集器的多线程版本,除了多线程以外,其余行为都和 Serial 收集器一样。
- 新生代并行,老年代串行,新生代复制算法、老年代标记-压缩。
- 默认开启的收集线程数与 CPU 的数量相同。
- 使用方法:-XX:+UseParNewGC 设定 ParNew 收集。
- 可以使用 -XX:ParallelGCThreads 参数来限制垃圾收集的线程数。
- 除了 Serial 收集器外,目前只有 ParNew 收集器能够与 CMS 收集器配合工作,CMS 收集器是 HotSpot 在 JDK 1.5 时推出的垃圾收集器。

4.2.3 Parallel 收集器
- Parallel Scavenge 收集器类似 ParNew 收集器,Parallel 收集器更关注系统的吞吐量。
- 可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量。
- 可以通过参数控制 GC 的时间不大于多少毫秒或者比例。
- 新生代复制算法、老年代标记-压缩。
- 使用方法:-XX:+UseParallelGC 设定使用 Parallel 收集器 + 老年代串行。
- 吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 运行垃圾收集时间),如果虚拟机一共运行 100 分钟,垃圾收集运行了 1 分钟,那么吞吐量就是 99%。
- 停顿时间越短就越适合与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效的利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
- Parallel Scavenge 收集器提供了两个参数来精确控制吞吐量。
- 控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数,允许的值是一个大于 0 的毫秒数,收集器将尽可能在给定时间内完成垃圾收集。不过垃圾收集时间的缩短是以牺牲吞吐量和新生代空间为代价的,短的垃圾收集时间会导致更加频繁的垃圾收集行为,从而导致吞吐量的降低。
- 设置吞吐量大小的 -XX:GCTimeRatio 参数,值是一个大于 0 且小于 100 的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。如果设置为 19,那允许的最大 GC 时间就是总时间的 5%(1/(1+19))。默认是 99,也就是允许最大 1% 的垃圾收集时间。
- 参数 -XX:UseAdaptiveSizePolicy 是一个开关参数,打开后就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最适合的停顿时间或最大的吞吐量,这叫 GC 自适应的调节策略。这也是 Parallel Scavenge 收集器和 ParNew 收集器的一个重要区别。
4.2.4 Serial Old 收集器
- 老年代单线程收集器,Serial 收集器的老年代版本。
- 使用 " 标记-整理 " 算法。
- 这个收集器主要给 Client 模式下的虚拟机使用。
- 在 Serve 模式下有两个用途。
- 在 JDK 1.5 之前的版本中与 Parallel Scavenge 收集器搭配使用。
- 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
4.2.5 Parallel Old 收集器
- Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和 " 标记-整理 " 算法。
- 使用方法:-XX:+UseParallelOldGC 使用 Parallel 收集器 + 老年代并行。

4.2.6 并发标记扫描 CMS 收集器
- CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,也称为 并发低停顿收集器。
- -XX:+UseConcMarkSweepGC 使用 CMS 收集器。
- -XX:+ UseCMSCompactAtFullCollection FGC 后进行一次碎片整理,整理过程是独占的,会引起停顿时间变长。
- -XX:+CMSFullGCsBeforeCompaction 设置进行几次 FGC 后进行一次碎片整理。
- -XX:ParallelCMSThreads 设定 CMS 的线程数量(一般情况约等于可用 CPU 数量)。
- CMS 收集器基于 " 标记-清除 " 算法实现,整个过程分为 4 个步骤。
- 初始标记,仅仅标记 GC Roots 能直接关联到的对象,速度很快,需要停止服务(Stop The World)。
- 并发标记,进行 GC Roots Tracing 的过程,为了标记上一步集合中的存活对象,因为用户程序这段时间也在运行,所以并不能保证可以标记出所有的存活对象。
- 重新标记,为了修正并发标记阶段因用户程序继续运作而导致标记变动的那一部分对象,采用多线程并行来提升效率,会停止服务(Stop The World),时间上远比并发标记短,较初始标记稍长。
- 并发清除,并发收集垃圾对象,可以与用户线程一起工作。
- 整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以整体上,CMS 收集器的内存回收过程是与用户线程一共并发执行的。
- 其优点是并发收集、低停顿。
- 其缺点是产生大量空间碎片、并发阶段会降低吞吐量。
- CMS 收集器对 CPU 资源非常敏感。CMS 默认启动的回收线程数是(CPU 数量 + 3)/ 4,当 CPU 个数大于 4 时,垃圾收集线程使用不少于 25% 的 CPU 资源,当 CPU 个数不足时,CMS 对用户程序的影响很大。
- CMS 收集器无法处理浮动垃圾,可能出现 " Concurrent Mode Failure " 失败而导致另一次 FGC。
- CMS 使用标记-清除算法,会产生内存碎片。
4.2.7 G1 收集器
- G1(Garbage first)收集器是最先进的收集器之一,是面向服务端的垃圾收集器。与其他收集器相比,G1收集器有如下优点。
- 并行与并发,有些收集器需要停顿的过程 G1 仍然可以通过并发的方式让用户程序继续执行。
- 分代收集,可以不使用其他收集器配合管理整个 Java 堆。
- 空间整合,使用标记-整理算法,不产生内存碎片。
- 可预测的停顿,G1 除了降低停顿外,还能建立可预测的停顿时间模型。
- G1 中也有分代的概念,不过使用 G1 收集器时,Java 堆的内存布局与其他收集器有很大的差别,它将整个 Java 堆划分为多个大小相等的独立区域(Region)。
- G1 收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划的避免在整个 Java 堆中进行全区域的垃圾收集。
- G1 跟踪各个 Region 里垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间的经验值),在后台维护一个优先列表,每次优先收集价值最大的那个 Region,这样就保证了在有限的时间内尽可能提高效率。
- G1 收集器的大致步骤。
- 初始标记,仅标记 GC Roots 能直接关联到的对象。
- 并发标记,进行 GC Roots Tracing 的过程,并不能保证可以标记出所有的存活对象。这个阶段若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收。
- 最终标记,为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,G1 中采用了比 CMS 更快的初始快照算法 snapshot-at-the-beginning(SATB)。
- 筛选回收,首先排序各个 Region 的回收价值和成本,然后根据用户期望的 GC 停顿时间来制定回收计划,最后按计划回收一些价值高的 Region 中垃圾对象,回收时采用 " 复制 " 算法,从一个或多个 Region 复制存活对象到堆上的另一个空的 Region,并且在此过程中压缩和释放内存,需要停止服务(Stop The World)。

- 默认:-XX:+UseG1GC 设定使用 G1。
5. GC 参数
GC 参数 | 说明 |
---|---|
UseSerialGC | 虚拟机运行在 Client 模式的默认值,打开此开关参数后,使用 Serial+Serial Old 收集器组合进行垃圾收集。 |
UseParNewGC | 打开此开关参数后,使用 ParNew+Serial Old 收集器组合进行垃圾收集。 |
UseConcMarkSweepGC | 打开此开关参数后,使用 ParNew+CMS+Serial Old 收集器组合进行垃圾收集。Serial Old 作为 CMS 收集器出现 Concurrent Mode Failure 的备用垃圾收集器。 |
UseParallelGC | 虚拟机运行在 Server 模式的默认值,打开此开关参数后,使用 Parallel Scavenge+Serial Old 收集器组合进行垃圾收集。 |
UseParallelOldGC | 打开此开关参数后,使用 Parallel Scavenge+Parallel Old 收集器组合进行垃圾收集。 |
SurvivorRatio | 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden : Survivor = 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 收集器时生效。 |
CMSInitatingOccupancyFraction | 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集。默认值为 68%,仅在使用 CMS 收集器时生效。 |
UseCMSCompactAtFullCollection | 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用 CMS 收集器时生效。 |
CMSFullGCsBeforeCompaction | 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用 CMS 收集器时生效。 |
-XX:+PrintGC | 输出 GC 日志。 |
-XX:+PrintGCDetails | 输出 GC 的详细日志。 |
-XX:+PrintGCTimeStamps | 输出 GC 的时间戳(以基准时间的形式)。 |
-XX:+PrintGCDateStamps | 输出 GC 的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)。 |
-XX:+PrintHeapAtGC | 在进行 GC 的前后打印出堆的信息。 |
-Xloggc:../logs/gc.log | 日志文件的输出路径。 |
6. GC 日志
[GC (System.gc()) [PSYoungGen: 5243K->808K(76288K)] 70779K->66352K(251392K), 0.0009738 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 808K->0K(76288K)] [ParOldGen: 65544K->635K(175104K)] 66352K->635K(251392K), [Metaspace: 3205K->3205K(1056768K)], 0.0050875 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 76288K, used 655K [0x000000076af00000, 0x0000000770400000, 0x00000007c0000000)
eden space 65536K, 1% used [0x000000076af00000,0x000000076afa3ee8,0x000000076ef00000)
from space 10752K, 0% used [0x000000076ef00000,0x000000076ef00000,0x000000076f980000)
to space 10752K, 0% used [0x000000076f980000,0x000000076f980000,0x0000000770400000)
ParOldGen total 175104K, used 635K [0x00000006c0c00000, 0x00000006cb700000, 0x000000076af00000)
object space 175104K, 0% used [0x00000006c0c00000,0x00000006c0c9ef58,0x00000006cb700000)
Metaspace used 3211K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 349K, capacity 388K, committed 512K, reserved 1048576K
-
GC (System.gc()) 表示 YoungGC 日志。
- PSYoungGen: 5243K->808K(76288K) 表示 Parallel Scavenge 收集器,YoungGC 前新生代内存占用为 5243K,YoungGC 后新生代内存占用为 808K,新生代总内存大小为 76288K。
- 70779K->66352K(251392K) 表示 YoungGC 前 Java 堆内存占用 70779K,YoungGC 后 Java 堆内存占用 66352K,Java 堆总内存大小为 251392K。
- 0.0009738 secs 表示 YoungGC 耗时 0.0009738 secs。
- Times: user=0.00 sys=0.00, real=0.00 secs 表示 YoungGC 用户耗时 0.00,YoungGC 系统耗时 0.00,YoungGC 实际耗时 0.00。
-
Full GC (System.gc()) 表示 FGC 日志。
- PSYoungGen: 808K->0K(76288K) 表示 Parallel Scavenge 收集器,GC 前新生代内存占用为 808K,GC 后新生代内存占用为 0K,新生代总内存大小为 76288K。
- ParOldGen: 65544K->635K(175104K) 表示 Parallel Old 收集器,GC 前老年代内存占用为 65544K,GC 后老年代内存占用为 635K,老年代总内存大小为 175104K。
- 66352K->635K(251392K) 表示 GC 前 Java 堆内存占用 66352K,GC 后 Java 堆内存占用 635K,Java 堆总内存大小为 251392K。
- Metaspace: 3205K->3205K(1056768K) 表示 GC 前元空间内存占用 3205K,GC 后元空间内存占用 3205K,元空间总内存大小为 1056768K。
- 0.0050875 secs 表示 GC 耗时 0.0050875 secs。
- Times: user=0.00 sys=0.00, real=0.00 secs 表示 GC 用户耗时 0.00,GC 系统耗时 0.00,GC 实际耗时 0.00。
-
Heap 表示堆信息。
- PSYoungGen 表示新生代(使用 Parallel Scavenge 收集器),total 76288K, used 655K,内存总大小 76288K,使用 655K。
- eden space 65536K, 1% used 表示其中 Eden 区域内存大小 65536K,使用 1%。
- from space 10752K, 0% used 表示其中 From Survivor 区域内存大小 10752K,使用 0%。
- to space 10752K, 0% used 表示其中 To Survivor 区域内存大小 10752K,使用 0%。
- ParOldGen 表示老年代(使用 Parallel Old 收集器),total 175104K, used 635K,内存总大小 175104K,使用 635K。
- Metaspace 表示元空间。
- PSYoungGen(76288K)= Eden(65536K)+ From/To(10752K)
- Java 堆总内存(251392K)= PSYoungGen(76288K)+ ParOldGen(175104K)
- ParOldGen(175104K)≈ 2 * PSYoungGen(76288K)
- 元空间最大(1048576K)
参考资料
https://blog.csdn.net/sunjin9418/article/details/79603651
https://blog.csdn.net/tjiyu/article/details/53982412
https://blog.csdn.net/qq_36711757/article/details/80470820
https://blog.csdn.net/high2011/article/details/80177473
https://blog.csdn.net/heuguangxu/article/details/80542098
https://www.cnblogs.com/wrencai/p/5668621.html
https://blog.csdn.net/baiyan3212/article/details/81458697