欢迎您访问 王者荣耀赛事押注软件有限公司官方网站

王者荣耀赛事押注软件简介 联系我们

欢迎来电咨询

0567-75929513

王者荣耀赛事押注软件客户案例

全国服务热线

0567-75929513

技术过硬,据实报价

案例分类1

当前位置:主页 > 王者荣耀赛事押注软件客户案例 > 案例分类1 >

美团技术团队:从实际案例聊聊Java应用的GC优化

2021-10-25 00:45 已有人浏览
本文摘要:作者:录录,2016年加入美团点评,主要卖力北京业务宁静中心对接服务的后台研发事情。当Java法式性能达不到既定目的,且其他优化手段都已经穷尽时,通常需要调整垃圾接纳器来进一步提高性能,称为GC优化。但GC算法庞大,影响GC性能的参数众多,且参数调整又依赖于应用各自的特点,这些因素很大水平上增加了GC优化的难度。 即便如此,GC调优也不是无章可循,仍然有一些通用的思考方法。

王者荣耀赛事押注软件

作者:录录,2016年加入美团点评,主要卖力北京业务宁静中心对接服务的后台研发事情。当Java法式性能达不到既定目的,且其他优化手段都已经穷尽时,通常需要调整垃圾接纳器来进一步提高性能,称为GC优化。但GC算法庞大,影响GC性能的参数众多,且参数调整又依赖于应用各自的特点,这些因素很大水平上增加了GC优化的难度。

即便如此,GC调优也不是无章可循,仍然有一些通用的思考方法。本篇会先容这些通用的GC优化计谋和相关实践案例,主要包罗如下内容: > 优化前准备: 简朴回首JVM相关知识、先容GC优化的一些通用计谋。

> 优化方法: 先容调优的一般流程:明确优化目的→优化→跟踪优化效果。> 优化案例: 简述笔者所在团队遇到的GC问题以及优化方案。一、优化前的准备GC优化需知为了更好地明白本篇所先容的内容,你需要相识如下内容。1. GC相关基础知识,包罗但不限于: a) GC事情原理。

b) 明白新生代、暮年代、提升等术语寄义。c) 可以看懂GC日志。GC优化不能解决一切性能问题,它是最后的调优手段。如果对第一点中提及的知识点不是很熟悉,可以先阅读小结-JVM基础回首;如果已经很熟悉,可以跳过该节直接往下阅读。

JVM基础回首JVM内存结构简朴先容一下JVM内存结构和常见的垃圾接纳器。今世主流虚拟机(Hotspot VM)的垃圾接纳都接纳“分代接纳”的算法。“分代接纳”是基于这样一个事实:工具的生命周期差别,所以针对差别生命周期的工具可以接纳差别的接纳方式,以便提高接纳效率。Hotspot VM将内存划分为差别的物理区,就是“分代”思想的体现。

如图所示,JVM内存主要由新生代、暮年代、永久代组成。① 新生代(Young Generation):大多数工具在新生代中被建立,其中许多工具的生命周期很短。每次新生代的垃圾接纳(又称Minor GC)后只有少量工具存活,所以选用复制算法,只需要少量的复制成本就可以完成接纳。新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部门工具在Eden区中生成。

当Eden区满时,还存活的工具将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“提升”条件的工具将被复制到另外一个Survivor区。工具每履历一次Minor GC,年事加1,到达“提升年事阈值”后,被放到暮年代,这个历程也称为“提升”。

显然,“提升年事阈值”的巨细直接影响着工具在新生代中的停留时间,在Serial和ParNew GC两种接纳器中,“提升年事阈值”通过参数MaxTenuringThreshold设定,默认值为15。② 暮年代(Old Generation):在新生代中履历了N次垃圾接纳后仍然存活的工具,就会被放到年迈代,该区域中工具存活率高。暮年代的垃圾接纳(又称Major GC)通常使用“标志-清理”或“标志-整理”算法。

整堆包罗新生代和暮年代的垃圾接纳称为Full GC(HotSpot VM里,除了CMS之外,其它能收集暮年代的GC都市同时收集整个GC堆,包罗新生代)。③ 永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾接纳要接纳的Java工具关系不大。相对于新生代和年迈代来说,该区域的划分对垃圾接纳影响比力小。

常见垃圾接纳器差别的垃圾接纳器,适用于差别的场景。常用的垃圾接纳器:串行(Serial)接纳器是单线程的一个接纳器,简朴、易实现、效率高。并行(ParNew)接纳器是Serial的多线程版,可以充实的使用CPU资源,淘汰接纳的时间。吞吐量优先(Parallel Scavenge)接纳器,偏重于吞吐量的控制。

并发标志清除(CMS,Concurrent Mark Sweep)接纳器是一种以获取最短接纳停马上间为目的的接纳器,该接纳器是基于“标志-清除”算法实现的。GC日志每一种接纳器的日志花样都是由其自身的实现决议的,换而言之,每种接纳器的日志花样都可以纷歧样。但虚拟机设计者为了利便用户阅读,将各个接纳器的日志都维持一定的共性。JavaGC日志 中简朴先容了这些共性。

参数基本计谋各分区的巨细对GC的性能影响很大。如何将各分区调整到合适的巨细,分析活跃数据的巨细是很好的切入点。

活跃数据的巨细是指,应用法式稳定运行时恒久存活工具在堆中占用的空间巨细,也就是Full GC后堆中暮年代占用空间的巨细。可以通过GC日志中Full GC之后暮年代数据巨细得出,比力准确的方法是在法式稳定后,多次获取GC数据,通过取平均值的方式盘算活跃数据的巨细。活跃数据和各分区之间的比例关系如下(见参考文献1):空间倍数总巨细3-4 倍活跃数据的巨细新生代1-1.5 活跃数据的巨细暮年代2-3 倍活跃数据的巨细永久代1.2-1.5 倍Full GC后的永久代空间占用例如,凭据GC日志获得暮年代的活跃数据巨细为300M,那么各分区巨细可以设为:总堆:1200MB = 300MB × 4* 新生代:450MB = 300MB × 1.5* 暮年代: 750MB = 1200MB - 450MB*这部门设置仅仅是堆巨细的初始值,后面的优化中,可能会调整这些值,详细情况取决于应用法式的特性和需求。

二、优化步骤GC优化一般步骤可以归纳综合为:确定目的、优化参数、验收效果。确定目的明确应用法式的系统需求是性能优化的基础,系统的需求是指应用法式运行时某方面的要求,譬如: - 高可用,可用性到达几个9。

- 低延迟,请求必须几多毫秒内完成响应。- 高吞吐,每秒完成几多次事务。明确系统需求之所以重要,是因为上述性能指标间可能冲突。

好比通常情况下,缩小延迟的价格是降低吞吐量或者消耗更多的内存或者两者同时发生。由于笔者所在团队主要关注高可用和低延迟两项指标,所以接下来分析,如何量化GC时间和频率对于响应时间和可用性的影响。通过这个量化指标,可以盘算出当前GC情况对服务的影响,也能评估出GC优化后对响应时间的收益,这两点对于低延迟服务很重要。

举例:假设单元时间T内发生一次连续25ms的GC,接口平均响应时间为50ms,且请求匀称到达,凭据下图所示:那么有(50ms+25ms)/T比例的请求会受GC影响,其中GC前的50ms内到达的请求都市增加25ms,GC期间的25ms内到达的请求,会增加0-25ms不等,如果时间T内发生N次GC,受GC影响请求占比=(接口响应时间+GC时间)×N/T。可见无论降低单次GC时间还是降低GC次数N都可以有效淘汰GC对响应时间的影响。优化通过收集GC信息,联合系统需求,确定优化方案,例如选用合适的GC接纳器、重新设置内存比例、调整JVM参数等。

举行调整后,将差别的优化方案划分应用到多台机械上,然后比力这些机械上GC的性能差异,有针对性的做出选择,再通过不停的试验和视察,找到最合适的参数。验收优化效果将修改应用到所有服务器,判断优化效果是否切合预期,总结相关履历。接下来,我们通过三个案例来实践以上的优化流程和基本原则(本文中三个案例使用的垃圾接纳器均为ParNew+CMS,CMS失败时Serial Old替补)。

三、GC优化案例案例一 Major GC和Minor GC频繁确定目的服务情况:Minor GC每分钟100次 ,Major GC每4分钟一次,单次Minor GC耗时25ms,单次Major GC耗时200ms,接口响应时间50ms。由于这个服务要求低延时高可用,联合上文中提到的GC对服务响应时间的影响,盘算可知由于Minor GC的发生,12.5%的请求响应时间会增加,其中8.3%的请求响应时间会增加25ms,可见当前GC情况对响应时间影响较大。(50ms+25ms)× 100次/60000ms = 12.5%,50ms × 100次/60000ms = 8.3%。

优化目的:降低TP99、TP90时间。优化首先优化Minor GC频繁问题。通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间来降低Minor GC的频率。例如在相同的内存分配率的前提下,新生代中的Eden区增加一倍,Minor GC的次数就会淘汰一半。

这时许多人有这样的疑问,扩容Eden区虽然可以淘汰Minor GC的次数,但会增加单次Minor GC时间么?凭据上面公式,如果单次Minor GC时间也增加,很难保证最后的优化效果。我们联合下面情况来分析,单次Minor GC时间主要受哪些因素影响?是否和新生代巨细存在线性关系? 首先,单次Minor GC时间由以下两部门组成:T1(扫描新生代)和 T2(复制存活工具到Survivor区)如下图。(注:这里为了简化问题,我们认为T1只扫描新生代判断工具是否存活的时间,其实该阶段还需要扫描部门暮年代,后面案例中有详细形貌。

)扩容前:新生代容量为R ,假设工具A的存活时间为750ms,Minor GC距离500ms,那么本次Minor GC时间= T1(扫描新生代R)+T2(复制工具A到S)。扩容后:新生代容量为2R ,工具A的生命周期为750ms,那么Minor GC距离增加为1000ms,此时Minor GC工具A已不再存活,不需要把它复制到Survivor区,那么本次GC时间 = 2 × T1(扫描新生代R),没有T2复制时间。可见,扩容后,Minor GC时增加了T1(扫描时间),但省去T2(复制工具)的时间,更重要的是对于虚拟机来说,复制工具的成本要远高于扫描成本,所以,单次Minor GC时间更多取决于GC后存活工具的数量,而非Eden区的巨细。因此如果堆中短期工具许多,那么扩容新生代,单次Minor GC时间不会显著增加。

王者荣耀赛事押注软件

下面需要确认下服务中工具的生命周期漫衍情况:通过上图GC日志中两处红色框标志内容可知: 1. new threshold = 2(动态年事判断,工具的提升年事阈值为2),工具仅履历2次Minor GC后就提升到暮年代,这样暮年代会迅速被填满,直接导致了频繁的Major GC。2. Major GC后暮年代使用空间为300M+,意味着此时绝大多数(86% = 2G/2.3G)的工具已经不再存活,也就是说生命周期长的工具占比很小。由此可见,服务中存在大量短期暂时工具,扩容新生代空间后,Minor GC频率降低,工具在新生代获得充实接纳,只有生命周期长的工具才进入暮年代。

这样暮年代增速变慢,Major GC频率自然也会降低。优化效果通过扩容新生代为为原来的三倍,单次Minor GC时间增加小于5ms,频率下降了60%,服务响应时间TP90,TP99都下降了10ms+,服务可用性获得提升。调整前:调整后:小结如何选择各分区巨细应该依赖应用法式中工具生命周期的漫衍情况:如果应用存在大量的短期工具,应该选择较大的年轻代;如果存在相对较多的持久工具,暮年代应该适当增大。更多思考关于上文中提到提升年事阈值为2,许多同学有疑问,为什么设置了MaxTenuringThreshold=15,工具仍然仅履历2次Minor GC,就提升到暮年代?这里涉及到“动态年事盘算”的观点。

动态年事盘算:Hotspot遍历所有工具时,根据年事从小到大对其所占用的巨细举行累积,当累积的某个年事巨细凌驾了survivor区的一半时,取这个年事和MaxTenuringThreshold中更小的一个值,作为新的提升年事阈值。在本案例中,调优前:Survivor区 = 64M,desired survivor = 32M,此时Survivor区中age<=2的工具累计巨细为41M,41M大于32M,所以提升年事阈值被设置为2,下次Minor GC时将年事凌驾2的工具被提升到暮年代。JVM引入动态年事盘算,主要基于如下两点思量:如果牢固根据MaxTenuringThreshold设定的阈值作为提升条件: a)MaxTenuringThreshold设置的过大,原本应该提升的工具一直停留在Survivor区,直到Survivor区溢出,一旦溢出发生,Eden+Svuvivor中工具将不再依据年事全部提升到暮年代,这样工具老化的机制就失效了。

b)MaxTenuringThreshold设置的过小,“过早提升”即工具不能在新生代充实被接纳,大量短期工具被提升到暮年代,暮年代空间迅速增长,引起频繁的Major GC。分代接纳失去了意义,严重影响GC性能。

相同应用在差别时间的体现差别:特殊任务的执行或者流量身分的变化,都市导致工具的生命周期漫衍发生颠簸,那么牢固的阈值设定,因为无法动态适应变化,会造成和上面相同的问题。总结来说,为了更好的适应差别法式的内存情况,虚拟机并不总是要求工具年事必须到达Maxtenuringthreshhold再晋级暮年代。

案例二 请求岑岭期发生GC,导致服务可用性下降确定目的GC日志显示,岑岭期CMS在重标志(Remark)阶段耗时1.39s。Remark阶段是Stop-The-World(以下简称为STW)的,即在执行垃圾接纳时,Java应用法式中除了垃圾接纳器线程之外其他所有线程都被挂起,意味着在此期间,用户正常事情的线程全部被暂停下来,这是低延时服务不能接受的。本次优化目的是降低Remark时间。优化解决问题前,先回首一下CMS的四个主要阶段,以及各个阶段的事情内容。

下图展示了CMS各个阶段可以标志的工具,用差别颜色区分。1. Init-mark初始标志(STW) ,该阶段举行可达性分析,标志GC ROOT能直接关联到的工具,所以很快。2. Concurrent-mark并发标志,由前阶段标志过的绿色工具出发,所有可到达的工具都在本阶段中标志。3. Remark重标志(STW) ,暂停所有用户线程,重新扫描堆中的工具,举行可达性分析,标志在世的工具。

因为并发标志阶段是和用户线程并发执行的历程,所以该历程中可能有用户线程修改某些活跃工具的字段,指向了一个未标志过的工具,如下图中红色工具在并发标志开始时不行达,可是并行期间引用发生变化,变为工具可达,这个阶段需要重新标志出此类工具,防止在下一阶段被清理掉,这个历程也是需要STW的。特别需要注意一点,这个阶段是以新生代中工具为根来判断工具是否存活的。

4. 并发清理,举行并发的垃圾清理。可见,Remark阶段主要是通过扫描堆来判断工具是否存活。

那么准确判断工具是否存活,需要扫描哪些工具?CMS对暮年代做接纳,Remark阶段仅扫描暮年代是否可行?结论是不行行,原因如下:如果仅扫描暮年代中工具,即以暮年代中工具为根,判断工具是否存在引用,上图中,工具A因为引用存在新生代中,它在Remark阶段就不会被修正标志为可达,GC时会被错误接纳。新生代工具持有暮年代中工具的引用,这种情况称为“跨代引用”。因它的存在,Remark阶段必须扫描整个堆来判断工具是否存活,包罗图中灰色的不行达工具。

灰色工具已经不行达,但仍然需要扫描的原因:新生代GC和暮年代的GC是各自离开独立举行的,只有Minor GC时才会使用根搜索算法,标志新生代工具是否可达,也就是说虽然一些工具已经不行达,但在Minor GC发生前不会被标志为不行达,CMS也无法辨认哪些工具存活,只能全堆扫描(新生代+暮年代)。由此可见堆中工具的数目影响了Remark阶段耗时。

分析GC日志可以得出同样的纪律,Remark耗时>500ms时,新生代使用率都在75%以上。这样降低Remark阶段耗时问题转换成如何淘汰新生代工具数量。新生代中工具的特点是“朝生夕灭”,这样如果Remark前执行一次Minor GC,大部门工具就会被接纳。

CMS就接纳了这样的方式,在Remark前增加了一个可中断的并发预清理(CMS-concurrent-abortable-preclean),该阶段主要事情仍然是并发标志工具是否存活,只是这个历程可被中断。此阶段在Eden区使用凌驾2M时启动,固然2M是默认的阈值,可以通过参数修改。如果此阶段执行时等到了Minor GC,那么上述灰色工具将被接纳,Reamark阶段需要扫描的工具就少了。

王者荣耀赛事押注软件

除此之外CMS为了制止这个阶段没有等到Minor GC而陷入无限等候,提供了参数CMSMaxAbortablePrecleanTime ,默认为5s,寄义是如果可中断的预清理执行凌驾5s,不管发没发生Minor GC,都市中止此阶段,进入Remark。凭据GC日志红色标志2处显示,可中断的并发预清理执行了5.35s,凌驾了设置的5s被中断,期间没有等到Minor GC ,所以Remark时新生代中仍然有许多工具。对于这种情况,CMS提供CMSScavengeBeforeRemark参数,用来保证Remark前强制举行一次Minor GC。优化效果经由增加CMSScavengeBeforeRemark参数,单次执行时间>200ms的GC停顿消失,从监控上视察,GCtime和业务颠簸保持一致,不再有显着的毛刺。

小结通过案例分析相识到,由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了制止扫描时新生代有许多工具,增加了可中断的预清理阶段用来等候Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有许多工具,我们的调优计谋是,通过参数强制Remark前举行一次Minor GC,从而降低Remark阶段的时间。更多思考案例中只涉及暮年代GC,其实新生代GC存在同样的问题,即暮年代可能持有新生代工具引用,所以Minor GC时也必须扫描暮年代。

JVM是如何制止Minor GC时扫描全堆的? 经由统计信息显示,暮年代持有新生代工具引用的情况不足1%,凭据这一特性JVM引入了卡表(card table)来实现这一目的。如下图所示:卡表的详细计谋是将暮年代的空间分成巨细为512B的若干张卡(card)。卡表自己是单字节数组,数组中的每个元素对应着一张卡,当发生暮年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。

如上图所示,卡表3被标志为脏(卡表另有另外的作用,标识并发标志阶段哪些块被修悔改),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在暮年代指向新生代的引用。这样虚拟机通过空间换时间的方式,制止了全堆扫描。

总结来说,CMS的设计聚焦在获取最短的时延,为此它“尽心尽力”地做了许多事情,包罗只管让应用法式和GC线程并发、增加可中断的并发预清理阶段、引入卡表等,虽然这些操作牺牲了一定吞吐量但获得了更短的接纳停马上间。案例三 发生Stop-The-World的GC确定目的GC日志如下图(在GC日志中,Full GC是用来说明这次垃圾接纳的停顿类型,代表STW类型的GC,并不特指暮年代GC),凭据GC日志可知本次Full GC耗时1.23s。这个在线服务同样要求低时延高可用。

本次优化目的是降低单次STW接纳停马上间,提高可用性。优化首先,什么时候可能会触发STW的Full GC呢? 1. Perm空间不足; 2. CMS GC时泛起promotion failed和concurrent mode failure(concurrent mode failure发生的原因一般是CMS正在举行,可是由于暮年代空间不足,需要尽快接纳暮年代内里的不再被使用的工具,这时停止所有的线程,同时终止CMS,直接举行Serial Old GC); 3. 统计获得的Young GC提升到暮年代的平均巨细大于暮年代的剩余空间; 4. 主动触发Full GC(执行jmap -histo:live [pid])来制止碎片问题。然后,我们来逐一分析一下: - 清除原因2:如果是原因2中两种情况,日志中会有特殊标识,现在没有。- 清除原因3:凭据GC日志,其时暮年代使用量仅为20%,也不存在大于2G的大工具发生。

- 清除原因4:因为其时没有相关下令执行。- 锁定原因1:凭据日志发现Full GC后,Perm区变大了,推断是由于永久代空间不足容量扩展导致的。找到原因后解决方法有两种: 1. 通过把-XX:PermSize参数和-XX:MaxPermSize设置成一样,强制虚拟机在启动的时候就把永久代的容量牢固下来,制止运行时自动扩容。

2. CMS默认情况下不会接纳Perm区,通过参数CMSPermGenSweepingEnabled、CMSClassUnloadingEnabled ,可以让CMS在Perm区容量不足时对其接纳。由于该服务没有生成大量动态类,接纳Perm区收益不大,所以我们接纳方案1,启动时将Perm区巨细牢固,制止举行动态扩容。优化效果调整参数后,服务不再有Perm区扩容导致的STW GC发生。

小结对于性能要求很高的服务,建议将MaxPermSize和MinPermSize设置成一致(JDK8开始,Perm区完全消失,转而使用元空间。而元空间是直接存在内存中,不在JVM中),Xms和Xmx也设置为相同,这样可以淘汰内存自动扩容和收缩带来的性能损失。

虚拟机启动的时候就会把参数中所设定的内存全部化为私有,纵然扩容前有一部门内存不会被用户代码用到,这部门内存在虚拟机中被标识为虚拟内存,也不会交给其他历程使用。四、总结联合上述GC优化案例做个总结: 1. 首先再次声明,在举行GC优化之前,需要确认项目的架构和代码等已经没有优化空间。我们不能指望一个系统架构有缺陷或者代码条理优化没有穷尽的应用,通过GC优化令其性能到达一个质的飞跃。2. 其次,通过上述分析,可以看出虚拟机内部已有许多优化来保证应用的稳定运行,所以不要为了调优而调优,不妥的调优可能适得其反。

3. 最后,GC优化是一个系统而庞大的事情,没有万能的调优计谋可以满足所有的性能指标。GC优化必须建设在我们深入明白种种垃圾接纳器的基础上,才气有事半功倍的效果。本文中案例均来北京业务宁静中心(也称风控)对接服务的实践履历。

同时谢谢风控的小同伴们,是他们专业卖力的审阅,才让这篇文章越发完善。对于本文中涉及到的内容,接待大家指正和增补。原文:https://tech.meituan.com/2017/12/29/jvm-optimize.html。


本文关键词:王者荣耀赛事押注软件,美团,技术,团队,从,实际,案例,聊聊,Java,应

本文来源:王者荣耀赛事押注软件-www.zomia.cn