转载

HotSpot gc细节与经典垃圾收集器

上篇介绍了一些经典的垃圾收集算法和它们的优缺点 垃圾回收算法看这一篇就够了 今天跟着顾南的脚步,我们一起看一下HotSpot虚拟机中为了实现垃圾收集做了哪些事情,并且了解几个经典垃圾收集器的原理和适用场景,最后我们学会看gc日志,以及如何编写高质量的代码来优化垃圾收集器行为,话不多说我们开始;

原理篇

垃圾收集器要决定三件事

  • 判断哪些对象要回收
  • 什么时候回收
  • 怎么回收

判断哪些对象要回收

由于引用技术的循环引用等一些问题,jvm中都是使用的追踪式的垃圾收集器,那它们是如何判断一个对象是否要回收的呢,答案是依靠根结点枚举和可达性分析,在一些低延迟的垃圾回收器中(比如cms),可达性分析可以与用户线程并发进行,而不用停顿。

并发和并行的区别

并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线 程在协同工作,通常默认此时用户线程是处于等待状态。 并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾 收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于 垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的,如下图所示。在Java中,可作为 GC Root 的对象包括以下几种:

  • Java栈 和 Native 栈中所有引用的对象;
  • 两个方法区:方法区中的常量和静态变量;
  • 所有线程对象;
  • 所有跨代引用对象;
  • 和已知 GCRoots 对象同属一个CardTable 的其他对象。
HotSpot gc细节与经典垃圾收集器
图1 根结点枚举与可达性分析

这些根结点可能会随着程序的运行不断的变化,那收集器的根结点枚举这一步骤时都是必须暂停用户线程的,会面临stop th world的问题,在大多数web系统中,终端所有用户线程会给用户带来不好的体验,即使是号称不会停顿的cms,g1,zgc等收集器,在根结点枚举这一步骤,也是需要停顿的。 在HotSpot虚拟机中,使用一组称为OopMap的数据结构来解决这一问题的,在程序运行到全局安全点时,虚拟机可以可以在OopMap的协助下快速的实现先根结点枚举

安全点位置的选取是以是否让程序长时间执行的特征为标准选定的; 例如:方法调用,循环跳转,异常跳转等,只有具有这些功能的指令才会产生安全点;

如何在垃圾收集时让所有的线程都在最近的安全点停顿下来,有两种方案可选,抢先式终端、主动式中断 抢先式中断不需要线程主动配合,在垃圾收集时,系统吧用户线程全部中断,如果发现用户线程不在安全点上,那么恢复这个线程,让它一会再中断,直到它跑到安全点上。 主动式中断的思想是当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一 个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最 近的安全点上主动中断挂起。轮询标志的地方和安全点是重合的,另外还要加上所有创建对象和其他 需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集,避免没有足够内存分配新对象。

另一种中断位置叫做安全区域,如果用户线程处于sleep或者blocked状态,线程无法响应虚拟机中断请求,就不能到安全点挂起自己,这种情况就需要安全区域,在某一段代码片段中,引用关系不会发生变化,因此这这个区域中任何地方开始垃圾收集都是安全的。

在年轻代垃圾收集中,有些对象是直接和gcroot关联的,而有些对象是通过老年代间接的和gcroot关联 如果要在一次young gc中也扫描关联了gcroot的老年代才能进行,那么会增大回收器扫描的效率,如下图所示

HotSpot gc细节与经典垃圾收集器

gc root->object1->object2的引用是从gcroot到老年地啊再到年轻代的引用,如果想要跳过老年代优化younggc的效率,那么就要把object1加入到gcroot,维护跨代指针就是一种优化; 记录每个内存区域内有对象含有跨代指针,是一种叫做卡表实现的记忆集,一个卡页的内存中通常包含不止一个对象,只要卡页内有一个对象的字段存在着跨代指针,那么对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃 圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots中一并扫描。 在HotSpot虚拟机力通过写屏障技术维护卡表状态,写屏障可以看作在虚拟机层面对引用类型字段赋值的aop切面,在引用对象赋值时会产生一个环形通知,供程序执行额外的动作,赋值前后都在写屏障的覆盖范围内;

怎么回收

标记阶段是所有追踪式垃圾收集算法的共同特征,如果能较少这部分的停顿时间,那么收益将会是惠及每个追踪式垃圾收集器; 引入三色标记来作为辅助,把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色:

白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是 白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。

黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代 表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对 象不可能直接(不经过灰色对象)指向某个白色对象。

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

把扫描对象图想象为一股从灰色为波纹从黑色向白色推进的过程,那如果用户线程与收集器并发工作,收集器在对象图上标记颜色,用户线程在运行时修改关系,就会对对象的引用关系造成影响,使得本来要被清除的对象得以存活,这只是造成了“浮动垃圾”,而如果把本来存活的对象清除,那就会造成程序错误了; 因此解决并发扫描时,存活对象被清扫通常使用两种方法;

  • 增量更新
  • 原始快照

对象消失主要有两个操作引起

1 赋值器插入了一条或多条从黑色对象到白色对象的新引用; 2 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

增量更新是破坏第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新 插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫 描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象 了。

原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删 除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来 进行搜索。

以上的引用关系记录是通过写屏障实现的,cms使用的是增量更新,g1使用原始快照;

经典的垃圾收集器

查看自己服务器垃圾收集器

java -XX:+PrintCommandLineFlags -version

-XX:+UseParallelGC 也是java8 默认的垃圾收集器 UseParallelGC 即 Parallel Scavenge + Parallel Old

那就先熟悉下这两个垃圾收集器吧

1: Parallel Scavenge收集器

Parallel Scavenge收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器; 它的目标是达到一个可控制的吞吐量,所谓吞吐量就是处理器用户运行用户代码的时间与处理器消耗时间的比值,与cms这种尽可能缩短停顿时间的收集器不同,parallel scavenge在吞吐量的控制上下了更多功夫;

有同学问我,停顿时间越小,那程序运行时间越长,那吞吐量不就越高吗,那是不是说的是一回事, 其实不是,比如 程序运行2s 垃圾回收停顿1s,另一种运行10s 停顿2s,那么第一种停顿时间是1s小于第二种,但吞吐量要比第二种小的多

-XX:M axGCPauseM illis参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的 时间不超过用户设定值。不过大家不要异想天开地认为如果把这个参数的值设置得更小一点就能使得 系统的垃圾收集速度变得更快,垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的: 系统把新生代调得小一些,收集300MB新生代肯定比收集500MB快,但这也直接导致垃圾收集发生得 更频繁,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间 的确在下降,但吞吐量也降下来了。

-XX:GCTimeRat io参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的 比率,相当于吞吐量的倒数。譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5% (即1/(1+19)),默认值为99,即允许最大1%(即1/(1+99))的垃圾收集时间。

除上述两个参数之外,ParallelScavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得我们关注。这是一个开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略(GCErgonomics)

2: Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法,

HotSpot gc细节与经典垃圾收集器

我们服务器使用以上两种吞吐量优先的服务器看出我们更看重服务的吞吐量,允许程序的更长时间停顿,顺便说一句,如果程序的请求量更高,那么长时间的停顿可能会在没有自适应负载均衡系统的服务中造成高停顿,如果没做好限流的话可能会造成整个微服务的服务雪崩;

下面介绍一个以低延迟低停顿为主的垃圾收集器cms

3:CMS

大名鼎鼎的CMS(Concurrent Mark Sweep),是一种以获取最短挺短时间为牧鞭的的收集器,大部分java应用集中在基于浏览器的B/S系统的服务器上,这类应用通常会较为关注服务响应速度,希望停顿时间更短,以给用户带来良好的体验,cms基于标记清除的老年代回收器,主要分为四个步骤:

1)初始标记(CMS initial mark)

2)并发标记(CM S concurrent mark)

3)重新标记(CM S remark)

4)并发清除(CM S concurrent sweep)

其中需要停顿的是第一步初始标记和第三部重新标记,初始标记是标记gc roots能直接关联到的对象,速度很快 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行; 而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,前边有讲过cms是采用增量更新的方式处理的,这段的停顿时间稍长;最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的 对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

面向并发设计的程序都对处理器资源比较敏 感。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计 算能力)而导致应用程序变慢,降低总吞吐量;

CMS无法处理浮动垃圾,有可能出现并发失败造成一次 stop the world的full fc,因为标记清理过程中用户线程和垃圾回收线程并发运行,程序运行会产生新的垃圾对象,但这部分对象是出现在标记过程之后的,CMS无法在本次收集中清理掉,就要留到下一次收集,这一部分就是浮动垃圾,所以CMS不能等到老年代满了才开始收集,必须预留一部分空间给程序运行使用,java5默认是68%,java6的默认数值提升到了92%,那如果预留的空间还是无法满足程序运行时新对象分配,那么会出现一次并发失败,虚拟机会冻结用户线程,临时启用 serial old收集器(一个单线程的老年代垃圾收集器)来收集垃圾,这样的停顿时间就更长了,所以参数 -XX:CMSInitiatingOccupancyFraction设置得太高将会很容易导致 大量的并发失败产生,性能反而降低,用户应在生产环境中根据实际应用情况来权衡设置。

还有一个重要的关注点,CMS是一款基于标记-清除算法实现的垃圾收集器,这种算法会产生很多空间碎片,将会给大对象分配带来麻烦,会出现老年代还有很多空间,但是无法找到连续的足够大空间来分配对象不得不进行一次full gc,CMS收集器提供了一个参数,+UseCMS-CompactAtFullCollection开关参数,用于在CMS收集器不得不进行FullGC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,是无法并发的。这样空间碎片问题是解决了,但停顿时间又会变长,因此虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBefore-Compaction(此参数从JDK9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的FullGC之后,下一次进入FullGC前会先进行碎片整理(默认值为0,表示每次进入FullGC时都进行碎片整理)。

4: G1收集器

G1是一款主要面向服务端应用的垃圾收集器。HotSpot开发团队最初赋予它的期望是(在比较长 期的)未来可以替换掉JDK 5中发布的CMS收集器。现在这个期望目标已经实现过半了,JDK 9发布之 日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则 沦落至被声明为不推荐使用(Deprecate)的收集器[1]。如果对JDK 9及以上版本的HotSpot虚拟机使用 参数-XX:+UseConcMarkSweep GC来开启CM S收集器的话,用户会收到一个警告信息,提示CM S未 来将会被废弃

G1,可以面向全堆的任何组成部分回收,衡量标准不再是对象属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收效益最大,虽然它也是遵循分代收集理论,但是堆内存布局与其他收集器有非常明显的差异,G1不再坚持固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每个Region可以根据需要扮演新生代的Eden空间、Survivor空间,或者老年代空间,G1根据扮演不同角色的Region采用不同的策略去处理; Humongous区域是一种特殊的Region,专用来存储大对象,只要超过一个Region容量的一半就判定为大对象。

G1会去跟踪每个Region里边垃圾回收的价值大小,价值就是回收所获得的空间大小以及回收所需要时间的经验值,会在后台维护一个优先级列表,根据用户设定允许的收集停顿时间,优先处理收益最大的Region,使用记忆集来避免跨Region的扫描,G1维护一个哈希表作为记忆集,key是别的Region其实地址,value是个集合,存储元素是卡表的索引号,由于Region的数量比传统收集器的分代数量明显要多得多,因此G1收集器要比其他的传统垃 圾收集器有着更高的内存占用负担。

初始标记(Initial M arking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAM S 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。

并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SAT B记录下的在并发时有引用变动的对象。

最终标记(Final M arking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SAT B记录。

筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

HotSpot gc细节与经典垃圾收集器

从G1开始,最先进的垃圾收集器的设计导向都不约而同地变为追求能够应付应用的内存分配速率 (Allocation Rate),而不追求一次把整个Java堆全部清理干净。

目前在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其 优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间,当然,以上这些也仅是经验之谈,不 同应用需要量体裁衣地实际测试才能得出最合适的结论,随着HotSpot的开发者对G1的不断优化,也 会让对比结果继续向G1倾斜。

5: ZGC收集器

ZGC目标是在尽可能对吞吐量影响不太大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟; ZGC收集器是一款基于Region内存布局的,(暂时) 不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低 延迟为首要目标的一款垃圾收集器; 染色指针是一种直接将少量额外的信息存储在指针上的技术,染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理;

ZGC使用了多重映射(Multi-Mapping)将多个不同的虚拟内存地址映射到同一 个物理内存地址上,这是一种多对一映射,意味着ZGC在虚拟内存中看到的地址空间要比实际的堆内 存容量来得更大。把染色指针中的标志位看作是地址的分段符,那只要将这些不同的地址段都映射到 同一个物理内存空间,经过多重映射转换后,就可以使用染色指针正常进行寻址了

并发标记(ConcurrentMark):与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的阶段,前后也要经过类似于G1、Shenandoah的初始标记、最终标记(尽管ZGC中的名字不叫这些)的短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的。与G1、Shenandoah不同的是,ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked0、Marked1标志位。

并发预备重分配(ConcurrentPrepareforRelocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(RelocationSet)。重分配集与G1收集器的回收集(CollectionSet)还是有区别的,ZGC划分Region的目的并非为了像G1那样做收益优先的增量回收。相反,ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。因此,ZGC的重分配集只是决定了里面的存活对象会被重新复制到其他的Region中,里面的Region会被释放,而并不能说回收行为就只是针对这个集合里面的Region进行,因为标记过程是针对全堆的。此外,在JDK12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的。

并发重分配(ConcurrentRelocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(ForwardTable),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。这样做的好处是只有第一次访问旧对象会陷入转发,也就是只慢一次,对比Shenandoah的Brooks转发指针,那是每次对象访问都必须付出的固定开销,简单地说就是每次都慢,因此ZGC对用户程序的运行时负载要比Shenandoah来得更低一些。还有另外一个直接的好处是由于染色指针的存在,一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配(但是转发表还得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的。

并发重映射(ConcurrentRemap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,这一点从目标角度看是与Shenandoah并发引用更新阶段一样的,但是ZGC的并发重映射并不是一个必须要“迫切”去完成的任务,因为前面说过,即使是旧引用,它也是可以自愈的,最多只是第一次使用时多一次转发和修正操作。重映射清理这些旧引用的主要目的是为了不变慢(还有清理结束后可以释放转发表这样的附带收益),所以说这并不是很“迫切”。因此,ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所 有对象的,这样合并就节省了一次遍历对象图[9]的开销。一旦所有指针都被修正之后,原来记录新旧对象关系的转发表就可以释放掉了。

实战

查看gc日志

-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 日志文件的输出路径

尝试打出的gc日志

[GC (System.gc()) [PSYoungGen: 5022K->1152K(38400K)] 5022K->1160K(125952K), 0.0019083 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 1152K->0K(38400K)] [ParOldGen: 8K->1034K(87552K)] 1160K->1034K(125952K), [Metaspace: 3139K->3139K(1056768K)], 0.0045528 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

Heap PSYoungGen total 38400K, used 333K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000) eden space 33280K, 1% used [0x0000000795580000,0x00000007955d34a8,0x0000000797600000) from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000) to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000) ParOldGen total 87552K, used 1034K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000) object space 87552K, 1% used [0x0000000740000000,0x0000000740102bb0,0x0000000745580000) Metaspace used 3147K, capacity 4564K, committed 4864K, reserved 1056768K class space used 340K, capacity 388K, committed 512K, reserved 1048576K

HotSpot gc细节与经典垃圾收集器
HotSpot gc细节与经典垃圾收集器

我的服务器20秒左右一次young gc,没有full gc,说明运行状况良好 如果频繁full gc,会引起频繁停顿,以下情况会导致fullgc

1、System.gc()方法的调用 2、老年代空间不足 3、永生区空间不足 4、CMS GC时出现concurrent mode failure 5、堆中分配很大的对象

借助工具排查问题

jps查处进程id为5280

jmap -dump:format=b,file=temp.dump 5280 把文件dump出来,再通过jvisualvm分析对象的引用链的方式来定位具体频繁创建对象的地方。

结尾总结

今天学习了追踪式垃圾收集的过程,包括根结点枚举,并发可达性分析,三色标记,跨代(Region)引用的卡表,写屏障,之后又熟悉了几个java中经典的垃圾收集器,分析了各个经典垃圾收集器的优缺点和使用场景,今天,大多数互联网公司都在用java8,等下一个java11的时代来临,我们可以大范围的使用java11的zgc,相信这一天就快到来了,最后我们学习了查看gc日志的方式,以及简单讲解了如何排查频繁full gc,那今天就到这了,喜欢的朋友一键三连把,手动狗头。

下集预告

预先善其事必先利其器,下期聊下jmap类似的java自带的排查问题工具

原文  https://juejin.im/post/5eeb30def265da029d3407e6
正文到此结束
Loading...