GC入门超详解

GC

我们知道在整个JVM管理的内存中,程序计数器、虚拟机栈和本地方法栈都是属于线程私有的,因此这几个区域的内存分配和内存回收都具备确定性,不需要考虑内存回收,因为当方法或线程结束的时候,内存自然就被释放了

而Java堆Heap则是由垃圾收集器来管理,有着很显著的不确定性:只有程序运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分的内存分配和回收是动态的,垃圾收集器关注的正是这部分Heap内存该如何管理

一、何为垃圾?

1、垃圾概念

在Heap空间存放着Java世界中几乎所有的对象实例,当Heap空间不足需要进行垃圾回收的时候,首先就要确定哪些对象还 “ 活着 ”,哪些对象已经 “ 死亡 ”

当没有任何引用指向的一个对象或者一堆对象,就认为这些对象不可能再被任何途径使用,也就没有存在价值,就被认定为垃圾

2、再谈引用

1、传统引用定义

如果reference类型的数据中存储的数值代表另外一块内存的起始地址,就称该reference数据时代表某块内存、某个对象的引用

这种定义并没有什么不对,但是有点狭隘和老旧,在这种定义下一个对象只有 “被引用” 和 “未被引用” 两种状态。

在目前情况下我们希望能这样描述一类对象:当内存空间还足够时,能够保留在内存之中,如果内存空间在进行垃圾回收之后仍然非常紧张,此时就可以抛弃此类对象——很多系统的缓存功能符合这样的应用场景

2、现在引用定义

在JDK1.2之后,Java对引用的概念进行了扩充,目前的引用分类为四种——强、软、弱、虚,这四种引用的强度依次逐渐减弱

  • 强引用:最传统的引用定义,是代码中普遍存在的引用赋值,即类似 “Object o = new Object()” 这种引用关 系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收被引用的对象
  • 软引用:用来描述一些还有用,但非必须的对象。只被软引用关联的对象,在系统将要发生内存溢出前,才会把这类对象列到回收范围之内进行二次回收。软引用关联的对象遇到垃圾回收的时候,未必会被回收,只有内存不够的时候才会回收软引用——软引用非常适合做缓存使用
  • 弱引用:也是用来描述一些还有用,但非必须的对象,但是强度比软引用更弱一些。软引用关联的对象遇到垃圾回收的时候,未必会被回收,只有内存不够的时候才会回收软引用,而弱引用只要遭遇到GC就会回收——弱引用非常适合用在容器里
  • 虚引用:也称 “幽灵引用” ,基本没用,了解即可

这里只是大致说明了一下强、软、弱、虚四种引用,为了不喧宾夺主,详细细节另外扩充。

二、如何定位垃圾

1、引用计数法

Reference Count——引用计数法

在对象内存中,有一块区域存放一个计数器,当有一个引用指向此对象计数器就加一,当有一个引用断开计数器就减一,当计数器为0时此对象就被认定为垃圾

在这里插入图片描述

但是看似简单的引用计数法需要大量额外处理才能保证正常工作,而且有一个引用计数法无法解决的问题——循环引用

在这里插入图片描述

由于引用计数法的局限,所以在JVM并没有使用引用计数法来管理内存

2、可达性分析算法(重要)

可达性分析算法——Reachability Analysis:当前主流的程序语言都是通过可达性分析算法来判定对象是否存活,比如JVM

这个算法的核心思路就是通过一系列称为 “ GC Roots” 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程所走过的路径称之为 “引用链” ,如果某个对象到GC Roots间没有任何引用链相连(即不可达),则认为此对象时不可能被再次使用,认定为垃圾

在这里插入图片描述

三、如何回收垃圾

1、标记清除算法

清除算法——Mark Sweep,最早也是最基础的垃圾收集算法

算法执行过程

算法分为 “ 标记 ” 和 “ 清除 ” 两个阶段:首先标记所有需要回收的对象,然后统一回收所有被标记的对象;也可以反过来,首先标记存活的对象,然后统一回收所有未被标记的对象

算法特点

主要缺点有两个:

  1. 执行效率不稳定:需要两遍扫描,如果大部分对象是需要被回收的,需要进行大量的标记和清除的动作,执行效率随对象数量的增长而降低
  2. 容易产生碎片:标记、清除之后会产生大量的不连续的内存碎片

在这里插入图片描述

适用于存活对象比较多的情况下

2、标记复制算法

算法执行过程

标记复制算法——Copy,为了解决标记—清除算法面对大量可回收对象时效率执行低的问题,使用 “ 半区复制” 算法,将内存分为容量大小的两块,每次只使用其中一半。当一半内存满了,只需一遍扫描筛选所有还活着的对象,并把存活对象复制到另一半,并把这一半内存整体清空。

当只有少数对象是存活的时候,只需要复制少数对象即可,而且每次都是针对整个半区进行内存回收,不会产生内存碎片

算法特点

  1. 内存使用率低:可以发现我们每次有效使用的内存只有一半,空间大量浪费
  2. 适合存活对象较少的情况,实现简单,只需要扫描一次运行高效,且不会产生内存碎片

在这里插入图片描述

由于普通标记—复制算法要求保留一半区域,造成严重的内存浪费,现代虚拟机采用了一种更优化的半区复制分代策略——“Apple式回收” :把新生代分为一块较大的Eden区和两块较小的Survivor空间,每次分配内存只使用Eden区和一块Survivor。当新生代发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,直接把Eden和已经用过的Survivor全体清空,不需要再次辨别,效率较高

需要在两个空间之间来回导,复制的过程效率也会降低一些

3、标记整理算法

标记整理算法——Mark Compact,不是直接对可回收对象进行清理,而是让所有可存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

在这里插入图片描述

在一个空间内部导,而且不产生内存碎片,是很多Old区的垃圾回收算法

4、总结

三种垃圾回收算法各有优劣,不同的垃圾回收算法适用于不同的场景,没有说哪一个一定比哪一个好,不同的垃圾收集器根据场景选择,使用不同的垃圾回收算法

四、堆内存Heap分代模型

1、分代模型

当前商业虚拟机的垃圾收集器几乎都遵循 “ 分代收集 ”,它建立在两个分代假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的,存活时间较短
  • 强分代假说:熬过越多次垃圾回收的对象,我们认为其越难以消亡

这两个假说奠定了垃圾收集器的设计原则:收集器应该将Heap分为不同的区域,讲对象依据其年龄(熬过垃圾回收的次数)分配到不同的区域之中存储

  • 新生代:如果一个区域中的大多数对象都是朝生夕灭,难以熬过垃圾收集过程,就把这个区域集中放在一起,每次回收只关注如何保留少量存活,而不是标记那些大量要被回收的对象,就能以较低代价回收到大量内存空间,这个区域称为新生代
  • 老年代:如果都是难以消除的对象,我们认为它可能还会长久存在,就把他们集中在一起,只需要以较低频率来回收这个区域,即减少垃圾回收频率也保证内存空间的有效利用

2、逻辑分代和物理分代

分代又分为逻辑分代和物理分代:

  • 逻辑分代:给内存做一些概念上的区分,某些内存块称为什么,分配什么数据,但是在物理介质上并不连续
  • 物理分代:把真正的物理内存划分成不同区域,一块区域确实是新生代,一块是老年代

不同的垃圾回收器其逻辑分代和物理分代是不同的:

​ G1是逻辑分代,物理不分代

​ 除此之外其他的所有垃圾收集器,不仅逻辑分代而且物理也分代

3、Heap逻辑分区

在这里插入图片描述

整个Heap在逻辑上分为新生代和老年代,而为了避免复制算法的内存空间浪费,又把新生代分为Eden区和两个Survivor区,大小比例为 8:1:1,整个新生代和老年代大小比例为 1:2

不同的区域的对象存活时间是不同的,应该选择使用不同的垃圾回收算法:

  • Eden区使用标记—复制算法
  • Old区使用标记—整理算法

5、GC概念

MinorGC/YGC:年轻代空间耗尽时触发,凡是在年轻代回收过程的都叫MinorGC/YGC

MajorGC/FullGC:在老年代无法继续分配空间时触发,整个Heap的新生代、老年代内存同时回收

五、GC前置知识补充

1、STW

STW:stop—the—world,一旦发生STW,除了垃圾回收器线程,其余用户程序线程都停止运行。如果出现长时间的STW,就会造成系统卡顿甚至系统假死,客户体验度极差,这是生产环境中不能忍受的

2、安全点—SavePoint

安全点:SavePoint。用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须要执行到达安全点后才能够暂停。比如线程正在解锁unlock,就必须要等它unlock完成才能停止运行,开始垃圾收集

3、记忆表和卡表

处理年轻代与老年代之间对象的引用关系,用来处理跨代指针,避免YGC的时候还要扫描整个老年代

避免全量扫描Old区,相当于拿空间换时间

4、并发标记算法——三色标记算法(并发垃圾回收器核心)

三色标记算法:并发的可达性分析——垃圾收集器基本都是基于并发可达性分析算法判定对象是否存活,可达性分析算法要求全过程必须保证一致性,意味着必须要停顿用户的线程,对象越多,标记对象的间停顿时间越长,为了降低用户线程的停顿,引入了三色标记算法:

白色:表示对象尚未被垃圾回收器访问过

黑色:表示对象已经被垃圾收集器访问过

灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少还存在一个引用没有被扫描过

难点:在标记对象的过程中,对象引用关系正在发生改变,有可能就会产生一种事故——漏标

三色标记中间会产生一种情况:漏标——在并发标记的过程中,会有些本该是live object的对象无法被扫描到,本来应该是有用的对象,但是未被标记到会被当作垃圾回收掉,就会产生大问题,解决问题有两种:

  • 增量更新:Incremental Update——CMS使用
  • 原始快照:Snapshot At The Beginning,简称SATB——G1使用

面试题:为什么G1并发标记时处理漏标情况,使用SATB而不使用增量更新?

这里的前置知识补充,尤其是三色标记和增量更新都只是简单的概念描述,详情参考《深入理解Java虚拟机》P81页

六、常见垃圾回收器

在这里插入图片描述

1、Serial + Serial Old

Serial:伴随JDK诞生,最早的垃圾收集器,基于标记复制算法,是单线程垃圾回收。

单线程工作的收集器,这里的 “ 单线程 ” 除了它只使用一个线程进行垃圾回收,更重要的是强调在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它手机结束。存在STW,对于很多应用是不能忍受的

在这里插入图片描述

由于是单线程处理,当面对大容量的内存的时候,比如堆内存很大,可能会产生较长时间的STW,对于用户来说不可忍受,所以现在很少使用Serial + Serial Old

形象点的例子是:一群小孩在屋里扔纸团,当屋子里满了之后,你妈进来一人给一巴掌,让你们在墙角罚站,她一个人把屋里打扫干净,你们才可以继续扔纸团玩。一间屋子你妈一个人很快就打扫完了,如果是在一个广场呢?你妈一个人打扫一个广场可要花费不少时间了

早期JDK内存空间很少,相当于一个房间,单线程还能处理;现在内存很大,相当于天安门广场,单线程处理垃圾就有心无力了

Serial Old只是Serial用在了老年代而已,采用算法是标记—整理算法,其他的处理逻辑是相同的

2、Parallel Scavenge + Parallel Old

Parallel Scavenge:多个线程并行垃圾回收,同样是基于标记—复制算法实现的收集器,存在STW

在这里插入图片描述

PO只是PS用在老年代而已采用算法是标记—整理算法,其他的处理逻辑是相同的

Parallel Scavenge在进行垃圾收集的时候,使用的是多个GC线程并行执行,效率较高

形象点的例子是:一群小孩在屋里扔纸团,当屋子里满了之后,你爸、你妈、你奶奶、你姥姥进来,让你们在墙角罚站,他们好几个人一起把屋里打扫干净,你们才可以继续扔纸团玩。

PS + PO是 “吞吐量优先收集器组合” ,高吞吐量可以高效率的利用处理器资源,尽快完成程序的运算任务,适合于在后台运算而不需要太多交互的分析任务。如果应用上线,没有进行任何的设置,PS + PO就是默认的垃圾回收组合

3、ParNew + CMS

Parallel New:对Parallel Scavenge的变种,做了加强和调整,用在年轻代,以便和CMS配合使用

在这里插入图片描述

CMS:Concurrent Mark Sweep(并发标记清除)是具有划时代意义的垃圾收集器,它是第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程与用户线程并发同时工作,是第一款 “并发低停顿收集器”

这里需要区别两个概念:

  • 并行:并行描述的是多个垃圾收集器线程之间的关系,说明同一时间可以有多条同样的线程在协同处理同一件工作,此时用户线程处于等待状态
  • 并发:并发描述的是垃圾收集器和用户线程间的关系,说明同一时间垃圾收集线程和用户线程都在运行;并不是说一类线程运行的时候,另一类线程只能等待

CMS是基于标记—清除算法实现,运作过程比上面的要更复杂,整个过程分为四步:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)(耗时最多阶段、使用三色标记算法、关键点)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

其中初始标记、重新标记仍然需要STW,但是这两步速度很快没有影响;而并发标记阶段耗时就较长,但是不需要STW;重新标记阶段则是为了修正并发标记期间的增量更新;最后是并发清除阶段,由于不需要移动存活对象,所以垃圾收集线程和用户线程可以并发执行

在这里插入图片描述

由于是追求低停顿垃圾回收的第一次成功尝试,它不完美,至少有一下三个明显的缺点:

  1. 占用线程(处理器的计算能力),导致用户程序变慢,降低吞吐量
  2. 无法处理浮动垃圾,导致一次完全STW的FullGC 的产生,一旦FGC,STW时间就会特别长
  3. 基于标记—清除算法,产生内存碎片。Old区内存碎片过多触发FullGC,就会导致超长时间的STW

形象点的例子是:一群小孩在屋里扔纸团,你爸你妈清理的时候,你们仍然可以玩耍,不需要罚站。

可以一边垃圾回收,同时用户线程不会STW停顿;相当于一边打扫垃圾,一边玩耍,不需要罚站;

PN + CMS的关注点就是尽可能地缩短垃圾收集时用户线程的停顿时间,是“短停顿优先收集器组合”,可以通过参数将默认的 PS + PO 修改成 PN + CMS

4、G1(推荐)

Garbage First:垃圾优先收集器,是主要面向服务端应用的垃圾收集器,JDK1.8就出现了G1,但是还不成熟,JDK1.9彻底取代PS + PO成为JDK的默认垃圾收集器,而CMS则沦落为被Oracle官方不推荐使用,并且后面CMS也被废弃了

在G1出现之前的所有垃圾收集器,包括CMS,垃圾收集的目标要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个Java堆(Full GC),而G1跳出了这个樊笼,它可以面向堆内存的任何部分来组成回收集(Collect Set,简称CSet)进行回收

G1开创的基于Region的堆内存布局是它能够实现 " 停顿时间模型 "的关键:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆分成多个大小相等的独立区域(Region),每一个Region都可以根据角色需要,扮演新生代的Eden区、Survivor区,或者Old区。收集器能够对扮演不同角色的Region采用不同的垃圾回收策略,非常灵活

在这里插入图片描述

G1每次回收的时候,是优先找存活时间最少(即垃圾最多)的region进行回收,这也是 Garbage First 名称的由来

G1突破了物理内存布局,使用了分而治之的思想,虽然逻辑上分代,仍然保持新生代和老年代的概念,但是物理上不分代了,新生代和老年代不再是固定的了,都是一系列区域的动态组合

在这里插入图片描述

G1收集器的运作过程大致分为四个阶段:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

可以发现,G1除了并发标记之外,其余阶段也是需要STW,也就是说它并不是纯粹的追求低延迟,官方对它的期望是:在延迟可控的情况下获得尽可能高的吞吐量,担当起 “ 全功能收集器 ” 的重任

延迟可控是G1收集器很强大的一个功能:设置不同的期望停顿时间,可使得G1在不同应用场景下取得吞吐量和低延迟的最佳平衡(通过自动调整Eden和Old区比例来实现)

G1回收分为三个阶段:

  • YGC:回收Eden空间
  • MixedGC:即回收Eden空间,也回收Old阶段,混合执行
  • FGC:回收整个Heap堆空间

这三个阶段并不是顺序执行的,而是在满足一定条件之后会触发

G1特点:

  • 并发收集:和CMS相同,都是并发收集垃圾,都是使用三色标记算法
  • 压缩空闲时间,降低GC停顿时间
  • 延迟时间可控,尽量追求吞吐量
  • 适用于在追求响应时间的基础上,尽量追求吞吐量的场景
  • 整体来看是基于 “ 标记—整理 ”的算法实现,不会产生内存碎片

从G1开始,最先进的垃圾收集器都变为追求应付应用的内存分配速率,而不追求一次把整个Java堆全部清理干净。目标是使应用在分配内存,同时收集器也在收集垃圾,只要收集的速度能跟得上对象分配的速度,一切就能运作的很完美

目前在小内存应用上CMS的表现大概率仍然会优于G1,而大内存应用上G1则大多能发挥其优势,这个优劣势的平衡点通常是在6GB~8GB之间(经验之谈)

G1常见问题:

1、新老年代比例?

G1的新老年代比例进行了优化是动态的,默认比例在5%~60%之间,不用手动指定也不要手动指定,而是由G1在运行期间进行动态的、根据程序表现来动态的、自动的优化

2、G1是否分代?G1垃圾回收器会产生FGC吗?

  • G1在逻辑上分代,但是在物理上就不再连续和分代了
  • 当对象生成的特别快,垃圾回收线程来不及回收,对象分配不下,仍然还是会产生FGC

3、如果G1产生FGC,你应该做什么?

  • 扩内存
  • 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
  • 降低MixedGC触发的阈值(默认是45%),让MixedGC提早发生(关键点答案),FGC频率就会降低
  • 使用G1就应该尽量避免发生FGC,因为会很长时间的卡顿

5、总结

回首发现其实整个垃圾回收器的发展过程是随着内存不断变大而不断优化的:

  1. 内存很小:生产线程暂停,单线程回收小房间——Serial + Serial Old
  2. 内存大了一点:生产线程停顿,多线程回收公园垃圾——PS + PO
  3. 内存大了很多:生产线程不停顿,多线程并发回收天安门广场垃圾——PN + CMS or G1

在这里只是简单的学习了JDK1.8所能支持的垃圾收集器组合,里面还有很多详细的细节没有完全的解释清楚

而且目前为止,已经发行了JDK14,垃圾收集器也有了长远的发展,后续也有很多更优秀的垃圾收集器如Shenandoah、ZGC、Epsilon等,由于生产环境中短期时间内还用不到,暂且按下不表

七、对象自动分配内存

Java技术体系的自动内存管理,最根本的目标是自动化的解决两个问题:

  • 自动给对象分配内存(内存分配机制)
  • 自动回收分配给对象的内存(内存回收机制)

在上面我们详细讲述了内存回收的垃圾收集器体系及其运作原理,下面我们来学习JVM如何进行自动的内存分配,下面是对象分配的基本判断流程:

在这里插入图片描述

1、栈上分配

有一些小的对象,线程私有不会被其他线程共享,如果经过即时编译后能够被拆解为标量类型(能够直接参与运算,相当于看作是指令而不是对象),就会尝试先间接地在栈上分配,当栈弹出的时候对象随之消亡,不需要垃圾收集器来管理

2、线程本地分配(TLAB)

当对象无法标量替换,也就无法分配在栈上,此时就只能分配在Heap空间,一般分配在Eden区,而Eden区为了并发效率,让每一个线程保持一个线程独有的区域,称为TLAB,此时对象会优先分配在TLAB

由于所有的线程都会向Eden区分配对象,高并发情况下必然会产生线程的对同一内存的争用,所以JVM又设计了TLAB机制——每个线程占用Eden区 1% 的大小,这个内存区域线程独有,不会产生线程争用,提高内存分配效率

3、对象优先在 Eden 分配

对象的内存分配从概念上讲,应该都是在堆上分配,新生对象通常会分配在新生代中(E)。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,对Eden区进行内存回收

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

当对象很大(需要大量连续内存空间的Java对象),最经典的就是那种很长的字符串(如JSON对象),或者数量很庞大的数组,总之只要大小超过一个阙值,就可能直接分配在老年代(O)

我们在编写程序时要注意避免大对象:在分配空间时,它容易导致内存明明还有不少空间时就提前触发GC,以获取足够的连续空间才能安置好大对象,而当复制对象时,大对象就意味着高额的内存复制开销

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

多数垃圾收集器都采用了分代收集来管理内存,因此在对象内存分配时就必须能决策哪些存活对象应当放在新生代,哪些对象应该放在老年代

JVM通过存储在对象头部,给每一个对象定义了一个对象年龄(Age)计数器,当对象年龄达到阙值,就会进入到老年代,不同的垃圾收集器默认阙值不同:

对象何时进入老年代—经过的GC次数:

—Parallel Scavenge:15次

—CMS:6次

—G1:15次

6、动态对象年龄判定

为了能更好的适应不同程序的内存情况,JVM并不严格限制对象年龄达到阙值才能晋升老年代——如果在Survivor空间中相同年龄的所有对象大小的总和大于Sruvivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到年龄阙值

JVM自动管理、判定

7、空间分配担保

在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果这个条件成立,才可以确保这一次的Minor GC是安全的

新生代使用复制收集算法,为了提高内存利用率,只使用了一个Survivor空间作为备份,因此当出现大量对象在经过Minor GC之后仍然存活的情况——最极端情况下所有对象都存活,需要老年代进行分配担保,确保老年代可用空间能够容纳Survivor的所有对象

与贷款担保类似,贷款时需要有共同担保人,当贷款人无力偿还时,由担保人顶替还款义务,防止银行无法回收贷款

附录——GC常用参数

GC常用参数

  • -Xmn -Xms -Xmx -Xss(重要)
    年轻代 最小堆 最大堆 栈空间

  • -XX:+UseTLAB
    使用TLAB,默认打开

  • -XX:+PrintTLAB
    打印TLAB的使用情况

  • -XX:TLABSize
    设置TLAB大小

    TLAB建议不要进行调整

  • -XX:+DisableExplictGC
    让System.gc()不生效 ,FGC

  • -XX:+PrintGC

  • -XX:+PrintGCDetails

  • -XX:+PrintHeapAtGC

  • -XX:+PrintGCTimeStamps

  • -XX:+PrintVMOptions(常用)

    打印JVM运行时的参数

  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial(常用,重要)
    必须会用

  • -Xloggc:opt/log/gc.log

  • -XX:MaxTenuringThreshold
    升代年龄,最大值15

  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 …
    这些不建议设置

Parallel常用参数

  • -XX:SurvivorRatio
  • -XX:PreTenureSizeThreshold
    大对象到底多大
  • -XX:MaxTenuringThreshold
  • -XX:+ParallelGCThreads
    并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy
    自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC(重要)

    指定使用CMS

  • -XX:ParallelCMSThreads(重要)
    指定CMS垃圾回收线程并行数量

  • -XX:CMSInitiatingOccupancyFraction(重要)
    使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)

  • -XX:+UseCMSCompactAtFullCollection
    在CMS的时候对内存碎片进行整理,消除内存碎片,但是会延长STW时间

  • -XX:CMSFullGCsBeforeCompaction
    指定多少次CMS之后,才会触发内存碎片整理

  • -XX:CMSInitiatingPermOccupancyFraction
    达到什么比例时进行Perm回收

  • GCTimeRatio
    设置GC时间占用程序运行时间的百分比

  • -XX:MaxGCPauseMillis
    停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC(重要)

    指定使用G1垃圾收集器

  • -XX:MaxGCPauseMillis
    建议值,G1会尝试调整Young区的块数来达到这个值

  • -XX:GCPauseIntervalMillis
    GC的间隔时间

  • -XX:+G1HeapRegionSize
    每一个Region内存块大小,建议逐渐增大该值,1 2 4 8 16 32(随生产环境测试最优)
    随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长

  • G1NewSizePercent
    新生代最小比例,默认为5%

  • G1MaxNewSizePercent
    新生代最大比例,默认为60%

  • GCTimeRatio
    GC时间建议比例,G1会根据这个值调整堆空间

  • ConcGCThreads
    线程数量

  • InitiatingHeapOccupancyPercent
    启动G1的堆空间占用比例

猜你喜欢

转载自blog.csdn.net/qq_42583242/article/details/107827374
今日推荐