前言
JVM的GC机制绝对是很多程序员的福音,它让Java程序员省去了自己回收垃圾的烦恼。从而可以把大部分时间专注业务身上,大大提高了业务开发速度,让产品需求尽快的落地抢占市场。但是也正因为如此,导致很多Java程序员对JVM和GC知之甚少,以我愚见大家对JVM&GC不够了解的有几个原因:
-
门槛太高。我们平常接触的spring,dubbo,java集合&J.U.C,网上都有无数优秀的文章对其深入的分析。而且都是基于Java语言,我们在学习的过程中,可以自己很容易的debug源码更深入的了解。但是JVM则不然,它是C++开发的,能同时掌握C++和Java的程序员还是很少的,自己也不太好debug分析它的源码(就是编译jvm源码,都要折腾一番)。
-
有价值的系列文章太少。网上几乎没有完整体系的文章,优秀的书籍也很少(可能大家听过或者看过最多的就是周志明的深入理解Java虚拟机,这确实是JVM领域比较少见的佳作)。
-
接触的太少。虽然我们天天写Java代码,你写的每行代码JVM都会参与工作。但是很少进行GC调优,因为JVM是如此的优秀,绝大部分情况下,它只是默默的做你背后的女人(即使出问题了,也轮不到我们去排查,扎心了)。
对于JVM&GC,很多人没有去了解它,很多人也没机会去了解它,甚至有部分人都不原因去了解它。然而要想成为一名优秀的Java程序员,了解JVM和它的GC机制,写出JVM GC机制更喜欢的代码。并且你能知道JVM这个背后的女人是否发脾气了,还知道她发脾气的原因,这是必须要掌握的一门技术,是通往高级甚至优秀必须具备的技能。
这篇文章我不打算普及JVM&GC基础,而是主要讲解如何初步诊断GC是否正常,重点讲解诊断GC
。所以看这篇文章的前提,需要你对JVM有一定的了解,比如常用的垃圾回收器,Java堆的模型等。如果你还对JVM一无所知,并且确实想初步了解这门技术,那么请先花点时间看一下周志明的<<深入理解Java虚拟机>>,重点关注"第二部分 自动内存管理机制"。
GC概念纠正
初步诊断GC之前,先对GC中最常误解的几个概念普及一下。对GC机制有一定了解的同学都知道,GC主要有YoungGC,OldGC,FullGC(还有G1中独有的Mixed GC,收集整个young区以及部分Old区,提及的概率相对少很多,本篇文章不打算讲解),大概解释一下这三种GC,因为很多很多的同学对OldGC和FullGC有非常大的误解;
-
Young GC:应该是最没有歧义的一种GC了,只是有些地方称之为
Minor GC
,或者简称YGC
,都是没有问题的; -
Old GC:截止Java发展到现在JDK9为止,只单独回收Old区的只有CMS GC,并且我们常说的CMS是指它的
background collection
模式,这个模式包含CMS GC完整的5个阶段:初始化标记,并发标记,重新标记,并发清理,并发重置。由CMS的5个阶段可知,仍然有两个阶段需要STW,所以CMS并不是完全并发,而是Mostly Concurrent Mark Sweep,G1出来之前,CMS绝对是OLTP系统标配的垃圾回收器。 -
FullGC:有些地方称之为
Major GC
,Major GC通常是跟FullGC是等价的,都是收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“Major GC”的时候一定要问清楚他想要指的是上面的FullGC还是OldGC(参考R大的Major GC和Full GC的区别:https://www.zhihu.com/question/41922036/answer/93079526)。大家普遍对这个GC的误解绝对是最大的(我可以说至少有80%的人都有误解),首先对于ParallelOldGC即默认GC在Old满了以后触发的FullGC是没有问题的,jstat命令查看输出结果FGC
的值也会相应的+1,即发生了一次FGC。FGC误解主要来自最常用的ParNew+CMS组合,很多人误解FullGC可能是受到jstat
命令结果的影响,因为发生CMS GC时,FGC也会增大(但是会+2,这是因为CMS GC的初始化标记和重新标记都会完全的STW,从而FGC的值会+2)。但是,事实上这并没有发生FullGC。jstat命令结果中的FGC并不表示就一定发生了FullGC,很有可能只是发生了CMS GC而已。事实上,FullGC的触发条件非常苛刻,判断是否发生了FullGC最好的方法是通过GC日志,日志中如果有"full gc"的字样,就表示一定发生了Full GC。所以强烈建议生产环境开启GC日志,它的价值远大于它对性能的影响(不用权衡这个影响有多大,开启就对了)。
关于CMS的foreground collect模式,以及FullGC:
-
foreground collect
它发生的场景,比如业务线程请求分配内存