面试官问调优的话, 你先不要说调优,因为讲调优之前需要一些前置的内容,可以先从垃圾回收机制的类型开始讲起,以及GC的优化点在哪里?
一般讲完30分钟就过去了,但前提是都理解哈,不建议死记硬背.
PS + PO
这是JDK1.8默认的垃圾回收器. 他的特点是稳定性和吞吐较高.
是一款物理分代逻辑分代的垃圾回收期.
具体的区域分为伊甸园 幸存者 老年代 三大区域.
在讲具体回收顺序月之后我们先得讲一些垃圾回收算法.
引用计数算法:这个算法使用了指针的思想,有用的类互相引用,如果没有被引用到的,就默认是垃圾 进行回收.
但是有时候会出现浮动垃圾,也就是垃圾引用垃圾,会造成内存泄漏的情况,但是好处是实现简单,适用于python和GO.
根可达算法:这个算法在效率和实现难度中做了一个取舍,用较为复杂的算法实现难度换取了相对引用计数来说更高的性能,可以通过根节点获取下面所有的子节点,通过快速定位对象,来达到一个较快的效率.
标记清除算法:这个算法和我们的扫雷游戏很类似,在一个个的方格里里,你标记哪个是地雷,在标记结束之后,会对这些被标记的空间进行清除,但是这里包含着一个致命的缺点,就是他的空间是碎片化的,是非常浪费空间的.
标记压缩算法:这种方法可以压缩我们的空间,但是是用空间换时间,因为从效率上来说他是一边进行垃圾标记,一边进行压缩的,最后再进行清除,这样内存也是连续性的,但是显而易见的是他的缺点也十分明显,那么就是他的效率十分的低下.
现在说到我们最后一种算法了,这种算法叫做拷贝算法,也是我们目前应用在伊甸园区和幸存者区的算法.
首先在我们的JVM分代模型当中,分为伊甸园区(新生代),幸存者区(from,to),老年代.
举个例子现在我们往年轻代中放入10个对象,最后比如存活下来了一个,那么这一个对象就会被放到伊甸园区from区当中,这其中使用的就是根可达算法,根可达算法通过根节点像毛线团一样,从头抽到尾,把有用的对象都抽取过来,然后剩下在年轻代中的就都是等待被回收的垃圾了,那么他这时候如果执行一个内存擦除的功能,他的效率是非常高的,只需要给内存一个开始位置值和一个偏移量就行了,当这样循环往复往里面存取了之后,from区会进行垃圾回收,这里会运用到拷贝算法,虽然会损耗一部分的空间,但是可以提高我们YGC的效率,通过拷贝算法来把幸存的内存从from区转移到To区,之后from区和年轻代一起进行一次内存擦除,之后To区域满了之后会和From互换,保证To区的绝对干净,然后进行内存擦除,此时空间的默认比值为8:1:1,所以只会造成百分之10的浪费,也可以理解为使用了复制算法的变种,不等复制算法,当互换到达默认15次之后,这个是个参数可以设置,那么剩余的数据就会被放入到老年代当中,当老年代满了之后就会执行FullGC,出现STW.
补充:特别大的大对象以及动态年龄满足的对象会直接进入老年代. 也是导致FULL GC的罪魁祸首之一.
而我们JVM的调优 就是针对这个STW的次数 和 时间长度进行调优的.
优化点:内存太大清理不过来建议更换G1,内存小调整阈值看看,CPU100的话 根据jmap看对象排查一下代码,或者是使用arthas.
CMS
CMS是我们垃圾回收机制中里程碑级别的存在.这个里程碑指的是他开创了一种新的模式,加入了在流程上实现并发线程这一概念,可以一边执行业务线程一边执行垃圾回收线程,具体的执行步骤为标记根节点->标记子节点->重新标记->垃圾清除,其中前2步我们应该很容易理解,它指的其实就是从根节点出发,能够标记到的表示都不是垃圾,而第三步可能就不太明白了,当初我也上网查阅了一下OpenJDK官网上的资料,里面简要的介绍了一下我们在重新标记这一步骤所做的操作,英文翻译过来大致的意思就是减少我们并发执行下所做的变动,可以理解为减少一下容错率,最后一步就是将标记的垃圾进行清除了.
在这其中我们会引入一个算法,也就是大名顶顶的三色标记算法,里面变为黑色,白色,灰色三色. 白色代表未扫描到,灰色代表当前节点已扫描到但没有全部扫描完毕,黑色代表节点下全部扫描完毕.
举个例子有个student对象,其中有2个属性name和age.根据三色标记算法,CMS先会去寻找根节点student,然后依次给下面的子属性上色,如果没有全部上色完毕,那么根节点就是灰色,如果还没有寻找,那么就是白色,如果全部寻找到了,那么就是黑色,所以一个完全被标记的学生对象,就应该是全黑的.
听起来CMS好像很高大上,但是实际上它的问题还是很多的,现在我们来说说它的问题
1.浮动垃圾. 如果在清理期间,内存因为浮动垃圾满了,那么之后我们的CMS就会调用单线程进行FULL GC,这个效率是非常低的.
2.内存碎片化.如果在清理期间,塞入大对象的话,因为CMS使用的是标记清除算法,所以会出现内存碎片化的情况,导致内存利用率不是特别高,如果此时有大对象进来把空间撑爆了,那么之后我们的CMS也会调用单线程进行FULL GC,这个效率是非常低的,同上,至今不知道CMS为什么要用单线程进行FULL GC,听说是研发资金的原因,为了以后退出更好的GC.
应对CMS的问题,最好的建议就是切换垃圾回收器,把CMS切换成G1,因为我们业务卡顿的很大概率可能不是调优可以解决的,如果实在是需要使用CMS并且要对其进行优化的话,建议调整他的阈值或者是加大内存,尽可能的让他不要进行FULL GC,这样就不会出现单线程清理垃圾的问题了.
G1
G1可以理解为CMS的升级版本,他的响应时间大致为200ms,大致流程和CMS差不多,只不过GC分为了YGC,MixedGC和FullGC,中间多了一个混合回收,只不过他实现了逻辑上的分区,这里引入了一个新的名词Region区,不再是按照以往的垃圾回收器用代进行分配了,这里是分成一个个的Region区,每个区都分别代表伊甸园区,幸存者区或者老年代区,大对象区等等,这个数量是不固定的,并且G1引入了自动优化这一个概念,在之前我们的年轻代的资源空间需要我们根据具体的业务场景去调整他的大小,但是在G1当中我们无需指定他的范围,他会自动根据我们的YGC次数来进行微调,其中引入了Cset和Rset的概念,其中Rset可以帮我们快速定位引用对象之间的关系,每个Region区中都有一个Rset,他相当于是位图,里面有一个个的卡表,其中1代表了被引用,0代表没有被引用,没有被引用的会被放入Cset当中,这是一个回收集合,在MixedGC当中被用来回收,他会优先回收垃圾比较多的region区,大大加快了我们G1的回收速度,也是优化的一个亮点.
总结优点:
1.使用了copying算法,避免了空间的碎片化.
2.使用了位图Rset和垃圾收集集合Cset的概念,优化了垃圾回收的速度.
3.自动对年轻代进行优化.
4.混合GC优先会回收数据量比较大的Region区.
5.G1 能够减少大量的浮动垃圾 运用的是SATB快照技术 在并发标记会进行快照,记录下变更颜色过的引用,然后把这些引用重新扫描一遍,最大限度减少浮动垃圾的产生.
优化点:调整混合回收的阈值,尽量避免FullGC的出现.
FloatingGarbageObjectscandieduringaG1collectionandnotbecollected. G1usesatechniquecalledsnapshot-at-the-beginning (SATB) toguaranteethatallliveobjectsarefoundbythegarbagecollector. SATBstatesthatanyobjectthatisliveatthestartoftheconcurrentmarking (amarkingovertheentireheap) isconsideredliveforthepurposeofthecollection. SATBallowsfloatinggarbageinawayanalogoustothatofaCMSincrementalupdate. //补充一下很多网上的都说G1是没有垃圾的,实际上他是有的,具体详见openJDK官网 //https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html
调优场景
PS + PO 业务非常卡顿,首先升级了一下内存,升级为了100G,之后发现好了很多,但是卡顿时间变长了,最后使用了G1,连续6个月正常.