首页 > 其他分享 >深入理解JVM - 垃圾收集器

深入理解JVM - 垃圾收集器

时间:2022-11-04 14:06:23浏览次数:84  
标签:收集器 对象 回收 XX 线程 垃圾 JVM GC


垃圾回收主要是要解决3件事情:

  1. 那些内存需要回收?
  2. 如何回收?
  3. 什么时候回收?

术语解释

并行/并发

  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

STW

STW(Stop the World) 是JVM等待所有的用户线程进入safepoint并且阻塞,做一些全局性操作的行为,会造成全局停顿。通过参数-XX:+PrintSafepointStatistics和-XX:PrintSafepointStatisticsCount=1我们可以去查看安全点日志:

vmop    [threads: total initially_running wait_to_block]
65968.203: ForceAsyncSafepoint [931 1 2]
[time: spin block sync cleanup vmop] page_trap_count
[2255 0 2255 11 0] 1
  • spin:自旋阶段,等待用户线程进入安全点的时间。
  • block:让用户线程在安全点暂时阻塞的时间
  • sync:
  • cleanup:这个阶段是JVM做的一些内部的清理工作的时间。
  • vmop(VM Operation):JVM执行的一些全局性工作,例如GC,代码反优化。

那些内存需要回收

在强引用的情况下已经“死”了的对象就需要回收,在非强引用的情况下视情况回收。在java里面,几乎所有的对象实例都是在堆上分配,所以垃圾收集器第一件事情就是要判断堆上的这些实例那些是“死去”的,那些还“活着”。判断对象是否存活主要有两种算法,一种是“引用计数算法”,一种是“可达性分析算法”。

“死去”的标准是:不可能再被任何途径使用的对象。

引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

  • 优点:实现简单, 判断高效。
  • 缺点:当存在相互引用时,很难判断对象是否已经“死了”。

可达性分析算法

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。

深入理解JVM - 垃圾收集器_CMS垃圾收集器

GC Roots

在Java语言中,可作为GC Roots的对象包括下面几种:

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

对象引用类型

  • 强引用:强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用:表示对象还有用但并非必需,在系统将要发生内存溢出时回收。
  • 弱引用:也是表示对象还有用但并非必需,只要发生GC,该对象就会被回收。
  • 虚引用:虚引用也称为幽灵引用或者幻影引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

Java 4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用

引用类型

实现方式

被垃圾回收时间

用途

生存时间

强引用

Object obj = new Object()

从来不会

对象的一般状态

JVM停止运行时终止

软引用

SoftReference

出现OOM之前被回收

对象缓存

出现OOM之前

弱引用

WeakReference

GC发生时

对象缓存

下一次GC之前

虚引用

PhantomReference

GC发生时

在这个对象被收集器回收时收到一个系统通知

下一次GC之前

如何回收

垃圾收集算法

标记-清除算法

“标记-清除”(Mark-Sweep)算法:算法分为“标记”和“清除”两个阶段:

  • 标记:遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
  • 清除:遍历堆中所有的对象,将没有标记的对象全部清除掉。

深入理解JVM - 垃圾收集器_CMS垃圾收集器_02

  • 优点:高效(我个人觉得标记清除算法是所有垃圾收集算法中最高效的了,不知道为什么大部分资料都说,这个算法效率低,如有知晓原因的请多多指教)
  • 缺点:清除之后会产生大量的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉,适用于年轻代。

深入理解JVM - 垃圾收集器_Shenandoah_03

  • 优点:没有了内存碎片
  • 缺点:浪费内存空间,需要有额外的空间进行分配担保

复制算法适用于每次GC后存活对象很好的情况下,比如HotSpot虚拟机中的新生代,据统计新生代的对象存活率是2%。只不过HotSpot虚拟机并不是将新生代直接对半划分,而是分成了Eden和Survivor区,区域默认比值是​​Eden:Survivor0:Survivor1=8:1:1​​,这样划分后新生代浪费空间就只有10%了。当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion),分配担保会将Minor GC后存活的对象直接放到老年代中。

标记-整理算法

首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,适用于老年代。

深入理解JVM - 垃圾收集器_CMS垃圾收集器_04

  • 优点:没有了内存碎片,没有浪费内存空间
  • 缺点:每次回收都需要移动对象,需要更新引用

分代收集算法

上面说的三种算法是垃圾回收的基础算法,但是在虚拟机实现的过程中,不可能只使用其中一种算法来完成垃圾收集,所有引入了分代收集的概念。它根据对象存活周期的不同将内存划分为几块不同的区域, 如图:

深入理解JVM - 垃圾收集器_ZGC_05

在新生代中,因为每次Minor GC后,只有少量存活,所以比较适合复制算法。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,所以比较适合“标记—清理”或者“标记—整理”算法。

新生代对象首先在Eden进行分配,当Eden满了过后触发Minor GC,然后将存活的对象放到S0区域,再清空Eden区。当Eden再次满了过后触发Minor GC,然后将存活对象放到S1区域,再清空Eden和S0区,如此循环。当survivor区域不足以放下所有存活对象或者对象分代年龄达到临界值时,会将对象放到老年代中。当老年代满了后,会触发Full GC。

HotSpot的算法实现

枚举根节点

在垃圾收集过程中,枚举根节点会导致所有Java线程停顿(“Stop The World”)。为了能尽量减少对应用影响,我们需要尽量减少Java线程停顿时间。在前面我们列举了那些对象是GC Roots,但是我们怎么能快速找到这些GC Roots呢?因为我们越快找到这些对象,那么Java线程停顿时间就越短。

现在主流Java虚拟机都是用的是​​准确式GC​​,所以Java线程停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置。比如:虚拟机栈的本地变量表中,我们只需要找到其中的引用对象就行了,而非引用对象是不会成为GC Roots的,如果我们每次GC都需要进行全栈扫描去查找GC Roots,那么将增加Java线程的停顿时间。

在HotSpot中,它使用了一种OopMap的数据结构来存储GC Roots的信息,这样,在枚举根节点的时候,就可以避免全栈扫描了。但是什么时候来记录这些信息呢?

安全点

HotSpot可以快速且准确地完成GC Roots枚举,但是可能导致引用关系变化,或者说OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap,那将会需要大量的额外空间,这样GC的空间成本将会变得很高,所以就有了安全点(Safepoint)。程序只有运行到了安全点才会暂停下来,然后将变化的引用信息记录到OopMap中。

在HotSpot中方法调用、循环跳转、异常跳转等功能才能产生安全点。

当GC发生的时候,需要让所有的线程都到最近的安全点停下来。停顿方案有两种:

  • 抢先式中断:当GC发生的时,中断所有线程,如果线程不在安全点就,恢复线程到安全点上,几乎没有虚拟机采用这种方式。
  • 主动式中断:给线程设置一个中断标志位,线程执行过程中会主动去轮询这个标志位,当发现为中断标志位是true时,挂起当前线程(轮询标志的地方和安全点是重合的)。

安全区域

安全点可以解决正在执行中的线程到底安全点,记录对象引用信息。但是当线程处于Sleep或者Blocked状态的时候,线程无法响应JVM中断请求,所以安全点对这类线程就无效了,这时候就引入了安全区域(Safe Region)。

**安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。**我们也可以把Safe Region看做是被扩展了的Safepoint。

在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。

垃圾收集器

衡量垃圾收集器的三项最重要的指标是:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency),三者共同构成了一个“不可能三角”。

垃圾收集器就是内存回收的具体实现,主要有以下几种,以及组合方式:

深入理解JVM - 垃圾收集器_JVM垃圾收集算法_06

Serial / Serial Old收集器

Serial是一个单线程的新生代收集器,采用复制算法。Serial Old是一个单线程的老年代收集器,采用标记-整理算法。

  • 优点:简单高效,没有线程切换带来的开销;
  • 缺点:进行垃圾收集时,必须暂停所有工作线程,直到完成,停顿时间长;多核情况下无法充分使用资源。
  • 使用场景:适合内存不大的情况;单核服务器;

Serial/Serial Old收集器运行示意图:

深入理解JVM - 垃圾收集器_JVM垃圾收集算法_07

ParNew收集器

ParNew收集器起始就是Serial收集器的多线程版,是一个新生代收集器,采用复制算法

  • 优点:多核情况下,效率比Serial收集高
  • 缺点:进行垃圾收集时,必须暂停所有工作线程,直到完成,停顿时间长
  • 使用场景:多核服务器

ParNew/Serial Old收集器运行示意图:

深入理解JVM - 垃圾收集器_Shenandoah_08

  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

Parallel Scavenge / Parallel Old收集器

Parallel Scavenge收集器是一个新生代并行收集器,使用复制算法。Parallel Old收集器是一个老年代并行收集器,使用标记-整理算法。

  • 优点:吞吐量高,多核情况下能充分利用资源
  • 缺点:进行垃圾收集时,必须暂停所有工作线程,直到完成,停顿时间长
  • 使用场景:追求高吞吐量的服务,如:批处理等后台任务

吞吐量 = 运行用户代码时间 /(运行用户代码时间 +垃圾收集时间)

Parallel Scavenge/Parallel Old收集器运行示意图:

深入理解JVM - 垃圾收集器_G1垃圾收集器_09

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的老年代收集器,使用标记-清除算法。

CMS 收集器运行示意图:

深入理解JVM - 垃圾收集器_JVM垃圾收集算法_10

CMS 收集器主要包含4个阶段:

  • 初始标记(CMS initial mark):仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要停顿用户线程。
  • 并发标记(CMS concurrent mark):就是进行GC Roots Tracing的过程,不需要停顿用户线程。
  • 重新标记(CMS remark):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿用户线程,停顿时间比初始标记稍长,比并发标记短很多。
  • 并发清除(CMS concurrent sweep):清除垃圾对象。

CMC停顿时间短的原因是:最耗时的并发标记和并发清除都是可以和用户线程一起执行的。

  • 优点:并发收集、低停顿
  • 缺点:内存碎片多;导致应用程序变慢;浮动垃圾问题;
  • 使用场景:适用于用户交互类的服务
内存碎片问题

CMS使用的是标记—清除算法来实现的,所以就存在内存碎片的问题。当空间碎片过多,将会导致无法分配大对象,这时不得不提前触发一次Full GC。

  • -XX:+UseCMSCompactAtFullCollection:在CMS需要进行Full GC的时候,进行内存碎片的整理,这个过程无法并发(需要停顿用户线程),默认是开启。
  • -XX:CMSFullGCsBeforeCompaction:设置执行多好次Full GC后执行碎片整理,默认是0,表示每次都需要整理。
导致应用程序变慢

CMS在并发标记和并发清除阶段是和用户线程一起运行的,这是垃圾回收机制就会占用部分线程(CPU资源)进行垃圾回收,线程数量默认为(CPU数量+3)/ 4,这样就会导致应用程序变慢。

浮动垃圾问题

在并发清除阶段产生的垃圾,只能在下一次GC的时候被回收,这部分垃圾称为浮动垃圾(Floating Garbage)。CMS收集器因为无法处理浮动垃圾,可能会出现“Concurrent ModeFailure”失败,而导致临时启用Serial Old收集器来重新进行一次Full GC,这时停顿时间就很长了。因此CMS不能等到老年代满了才进行回收,需要留一部分空间,提供给在并发收集过程中运行的线程使用。

  • -XX:CMSInitiatingOccupancyFraction:设置老年代预留空间比例,JDK1.5默认68%,JDK1.6以后默认是92%;如果这个值太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

G1收集器

G1(Garbage-First)是一款面向服务端应用的垃圾收集器。同时适用于新生代和老年代,与其他GC收集器相比,G1具备如下特点:

  • 并行与并发:G1 能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短“Stop The World”停顿时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次GC的旧对象来获取更好的收集效果。
  • 空间整合:G1从整体来看是基于标记-整理算法实现的收集器,从局部(两个Region之间)上来看是基于复制算法实现的。这意味着G1运行期间不会产生内存空间碎片。此特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
  • 可预测的停顿:这是G1相对CMS的一大优势,G1除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在GC上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
G1堆模型

深入理解JVM - 垃圾收集器_G1垃圾收集器_11

它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

Humongous区域:专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。

建立可靠的停顿预测模型

G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

Remembered Set

记忆集(Remembered Set)用来记录跨区域的对象引用(比如,新生代与老年代之间的对象引用)。记忆集其实是一种“抽象”的数据结构,“卡表”(Card Table)是记忆集的一种实现,逻辑图如下:

深入理解JVM - 垃圾收集器_CMS垃圾收集器_12

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots中一并扫描。

当有其他分代区域中对象引用了本区域对象时,在为属性赋值的那一刻,虚拟机通过写屏障(Write Barrier)技术来维护记忆集的状态,使用写屏障后会带来“伪共享”问题。

伪共享:CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改不同变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。例如:线程1和线程2共享一个缓存行,线程1只读取缓存行中的变量1,线程2修改缓存行中的变量2,虽然线程1和线程2操作的是不同的变量,由于变量1和变量2同处于一个缓存行中,当变量2被修改后,缓存行失效,线程1要重新从主存中读取,因此导致缓存失效,从而产生性能问题。最简单的解决办法是填充,一个变量暂一个缓存行就不会有为共享了。

为了避免伪共享问题,可以不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏。

G1中每个Region都有一个与之对应的Remembered Set,在做YGC的时候,只需使用 年轻代中的region的Remembered Set作为根集,这些Remembered Set记录了old->young的跨代引用,避免了扫描整堆。而Mixed GC的时候,old generation中记录了old->old的Remembered Set,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。

Young GC

选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销,复制回收算法。

回收前:

深入理解JVM - 垃圾收集器_JVM垃圾收集算法_13

回收后:

深入理解JVM - 垃圾收集器_Shenandoah_14

Mixed GC

回收所有年轻代里的Region,外加根据全局并发标记(global concurrent marking)统计得出收集收益高的若干老年代Region,在用户指定的开销目标范围内尽可能选择收益高的老年代Region。Mixed GC不是full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(Full GC)来收集整个GC Heap。

回收前:

深入理解JVM - 垃圾收集器_Shenandoah_15

回收后:

深入理解JVM - 垃圾收集器_CMS垃圾收集器_16

G1收集器的运行过程

G1收集器的运作大致可划分为以下几个步骤:

深入理解JVM - 垃圾收集器_ZGC_17

  • 初始标记(Initial Marking):完成标记GC ROOTS 直接可达的对象,需要停顿用户线程(STW),耗时很短。
  • 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
  • 最终标记(Final Marking):用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录,这阶段需要停顿用户线程(STW),但是可并行执行。
  • 筛选回收(Live Data Counting and Evacuation):对Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来生成回收计划,然后将存活对象放到空闲Region中,在清理掉所有的旧Region,这阶段因为需要移动对象,所以会造成停顿用户线程(STW)

G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担。根据经验,G1至少要耗费大约相当于Java堆容量10%至20%的额外内存来维持收集器工作,原因如下:

  • 并发标记阶段当用户线程改变了对象引用关系时,G1使用了原始快照(SATB)算法来保证原本的对象关系不被打破。
  • G1也需要预留一部分内存,在垃圾收集过程中产生的新对象就需要在这部分预留内存里分配,如果回收速度赶不上分配速度,也会发生Full GC。
  • 建立可靠的停顿预测模型也需要额外消耗内存空间
  • G1的Region有很多所以Remembered Set所需要的空间也更大
G1相关参数

1 GC相关的其他主要的参数有:

  • ​-XX:G1HeapRegionSize=n​​​:设置Region大小,并非最终值,取值范围从1M-32M之间,且是2的指数,默认为​​size =(堆最小值+堆最大值)/ TARGET_REGION_NUMBER(2048) ,然后size取最靠近2的幂次数值, 并将size控制在[1M,32M]之间​
  • ​-XX:MaxGCPauseMillis​​:设置G1收集过程目标时间,默认值200ms,不是硬性条件
  • ​-XX:G1NewSizePercent​​:设置新生代最小值,默认值5%
  • ​-XX:G1MaxNewSizePercent​​:设置新生代最大值,默认值60%
  • ​-XX:ParallelGCThreads​​:设置STW期间,并行GC线程数
  • ​-XX:ConcGCThreads=n​​:设置并发标记阶段,并行执行的线程数
  • ​-XX:InitiatingHeapOccupancyPercent​​:设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous

Shenandoah垃圾收集器

Shenandoah是一款只有OpenJDK才会包含的收集器,最开始由RedHat公司独立发展后来贡献给了OpenJDK,相比G1主要改进点在于:

  1. 支持并发的整理算法,Shenandoah的回收阶段可以和用户线程并发执行;
  2. Shenandoah 目前不使用分代收集,也就是没有年轻代老年代的概念在里面了;
  3. Shenandoah 摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题的发生概率。

关键技术:

  • 连接矩阵
  • Brooks Pointer 转发指针技术

详情可以参考: ​​深入理解JVM - Shenandoah垃圾收集器​​

ZGC收集器

ZGC(Z Garbage Collector)是一款由Oracle公司研发的,以低延迟为首要目标的一款垃圾收集器。它是基于动态Region内存布局,(暂时)不设年龄分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的收集器。在JDK 11新加入,还在实验阶段,主要特点是:回收TB级内存(最大4T),停顿时间不超过10ms。

关键技术:

  1. 动态Region内存布局
  2. 染色指针(Colored Pointers)
  3. 读屏障(Load Barrier)
  4. 内存多重映射

什么时候回收

总的来说就是内存不足的时候进行垃圾回收。

Minor GC触发条件

当Eden区满时,且老年代的最大可用连续空间大于新生代所有对象的总和或者老年代最大连续空间比历次晋升的平均值大,就进行Minor GC,否则FullGC。

Full GC触发条件

  • 调用System.gc时,系统建议执行Full GC,但是不必然执行,不建议使用
  • 老年代空间不足
  • 方法区(永久代)空间不足
  • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
  • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

Mixed GC触发条件(G1)

Mixed GC的触发是由一些参数控制着:

  • G1HeapWastePercent参数:在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
  • G1MixedGCLiveThresholdPercent:old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet。
  • G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数。
  • G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量。

堆内存任何部分来组成的回收集合(Collection Set,一般简称CSet)

总结

收集器

收集器

串行、并行or并发

新生代/老年代

算法

适用场景

SWT

使用限制

Serial

串行

新生代

复制算法

单CPU环境下的Client模式

整个GC过程都会STW

都可用

Serial Old

串行

老年代

标记-整理

单CPU环境下的Client模式、CMS的后备预案

整个GC过程都会STW

都可用

ParNew

并行

新生代

复制算法

多CPU环境时在Server模式下与CMS配合

整个GC过程都会STW

JDK 1.3

Parallel Scavenge

并行

新生代

复制算法

在后台运算而不需要太多交互的任务

整个GC过程都会STW

JDK 1.4

Parallel Old

并行

老年代

标记-整理

在后台运算而不需要太多交互的任务

整个GC过程都会STW

JDK 1.4

CMS

并发

老年代

标记-清除

集中在互联网站或B/S系统服务端上的Java应用

初始标记、重新标记会STW,如果内存碎片过多会引起Serial Old 的 FullGC,停顿时间很长

JDK 1.5

G1

并发

both

标记-整理+复制算法

面向服务端应用,将来替换CMS

初始标记、重新标记和筛选回收会STW

JDK 1.7

Shenandoah

并发

无分代

并发整理+复制算法

低停顿应用

初始标记、最终标记、初始引用更新和最终引用更新会STW

OpenJDK 12

ZGC

并发

无分代

并发整理+复制算法

低停顿,高吞吐量应用

初始标记、重新标记会STW

JDK11和64位操作系统

垃圾回收器的重要参数

参数

描述

-XX:UseSerialGC

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

-XX:UseParNewGC

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

-XX:UseConcMarkSweepGC

打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用

-XX:UseParallelGC

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

-XX:UseParallelOldGC

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

SurvivorRatio

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

-XX:PretenureSizeThreshold

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

-XX:MaxTenuringThreshold

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

-XX:UseAdaptiveSizePolicy

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

-XX:HandlePromotionFailure

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

-XX:ParallelGCThreads

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

-XX:GCTimeRatio

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

-XX:MaxGCPauseMillis

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

-XX:CMSInitiatingOccupancyFraction

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

-XX:UseCMSCompactAtFullCollection

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

-XX:CMSFullGCsBeforeCompaction

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

-XX:+PrintGCDetails

虚拟机发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况

GC日志参数

参数

描述

-XX:+PrintGC

输出GC垃圾回收日志

-verbose:gc

与-XX:+PrintGC相同

-XX:+PrintGCDetail

输出详细的GC垃圾回收日志

-XX:+PrintGCTimeStamps

输出GC回收的时间戳

-XX:+PrintGCApplicationStoppedTIme

输出GC垃圾回收时所占用的停顿时间

-XX:+PrintGCApplicationConcurrentTime

输出GC并行回收时所占用的时间

-XX:+PrintHeapAtGC

输出GC前后详细的堆信息

-Xloggc:filename

把GC日志输出到filename指定的文件

-XX:+PrintClassHistogram

输出类信息

-XX:+PrintTLAB

输出TLAB空间使用情况

-XX:+PrintTenuringDistribution

输出每次minor GC后新的存活对象的年龄阈值

-XX:+PrintSafepointStatistics

输出安全点日志

-XX:PrintSafepointStatisticsCount=1

输出安全点日志的次数

-XX:+SafepointTimeout

开启进入安全点超时时输出日志,超时时间由SafepointTimeoutDelay指定

-XX:SafepointTimeoutDelay=2000

设置进去安全点的超时时间

-XX:ErrorFile=/xxx/java_error.log

JVM致命错误日志

​https://wjw465150.github.io/blog/java/my_data/JVM/Java%206%20JVM%E5%8F%82%E6%95%B0%E9%80%89%E9%A1%B9%E5%A4%A7%E5%85%A8.htm​

参考

《深入理解JAVA虚拟机》


标签:收集器,对象,回收,XX,线程,垃圾,JVM,GC
From: https://blog.51cto.com/u_15861563/5823708

相关文章

  • 详细了解JVM运行时内存
    详细了解JVM运行时内存1.程序计数器概念程序计数器也叫作PC寄存器,是一块很小的内存区域,可以看做是当前线程执行的字节码的行号指示器。字节码的解释工作就是通过改变程......
  • 【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列
    写在前面最近,一直有小伙伴让我整理下关于JVM的知识,经过十几天的收集与整理,初版算是整理出来了。希望对大家有所帮助。JDK是什么?JDK是用于支持Java程序开发的最小......
  • JVM学习笔记——垃圾回收篇
    JVM学习笔记——垃圾回收篇在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分我们会分为以下几部分进行介绍:判断垃圾回收对象垃圾回收算法分......
  • CMS垃圾收集器
    CMS是基于标记-清除算法的,收集的时候分为4个步骤:初始标记并发标记重新标记并发清除初始标记初始标记仅仅只是标记一下GCRoots能直接关联到的对象,所以速度很快。比......
  • JVM中的引用
    JVM中的引用引用的定义在JDK1.2版之前,Java里面的引用是很传统的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某......
  • JVM 常见线上问题 → CPU 100%、内存泄露 问题排查
    开心一刻明明是个小bug,但就是死活修不好,我特么心态崩了......前言Windows后文中用到了两个工具:​​ProcessorExplorer​​​、​​MAT​​,它们是什么,有什么用,怎么用,本......
  • JVM学习笔记——内存结构篇
    JVM学习笔记——内存结构篇在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分我们会分为以下几部分进行介绍:JVM整体介绍程序计数器虚拟机栈......
  • G1 垃圾收集器深入剖析(图文超详解)
    G1(GarbageFirst)垃圾收集器是目前垃圾回收技术最前沿的成果之一。G1同CMS垃圾回收器一样,关注最小时延的垃圾回收器,适合大尺寸堆内存的垃圾收集。但是,G1最大的特点是......
  • 垃圾回收与内存泄漏
    一、浏览器的垃圾回收机制1.垃圾回收的概念JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收......
  • 【Java编程思想读书笔记】第五章:垃圾回收机制
    参考书目:《Java编程思想》(第四版)友链:​​【读书笔记】Java重要知识点整理与汇总​​阅读《Java编程思想》(第四版)一书收获颇多,之所以想通过用博客记笔记的方式来读书,是因为......