jvm性能调优 - 17案例实战_每日上亿请求量的电商系统 老轻代垃圾回收参数如何优化

简介: jvm性能调优 - 17案例实战_每日上亿请求量的电商系统 老轻代垃圾回收参数如何优化

Pre

上一篇文章我们已经给大家介绍了一个每日百万日活以及上亿请求量的电商系统的案例背景,同时采用这个中型电商系统在大促期间的瞬时高峰下单场景,作为我们的JVM优化分析的一个场景,推测出来在大促高峰期,每秒每台机器会有300个下单请求。

进而推测出每秒钟会使用60MB的内存,然后根据这个背景推算出来了我们一台4核8G的机器上,应该如何合理的给JVM各个区域分配内存

进而可以保证每隔20多秒一次新生代GC后的100MB左右的存活对象,会进入200MB的Survivor区域内,一般不会因为Survivor塞不下或者是动态年龄判定规则让对象进入老年代中。

同时还根据Minor GC的频率,合理降低了大龄对象进入老年代的年龄,尽快让一些长期存活的对象赶紧进入老年代,不要停留在新生代里,如下图所示。

此时的JVM参数如下所示:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M  -XX:PermSize=256M -XX:MaxPermSize=256M  -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

在案例背景下什么时候对象会进入老年代?

接着我们来分析一个问题,在目前优化好的背景下,一般什么情况下会让一些对象进入老年代呢?

首先第一种情况,那绝对就是“-XX:MaxTenuringThreshold=5”这个参数会让在一两分钟内连续躲过5次Minor GC的对象迅速进入老年代中。

这种对象一般就是一些@Service、@Controller之类的注解标注的那种系统业务逻辑组件,这种对象实例一般全局就有一个实例就可以了,要一直使用的

所以一般会长期被GC Roots引用,这种对象一般不会太多,大概最多一个系统就几十MB这种对象。

所以此时类似这样的长期存活的对象就会进入老年代中,如下图所示。

此外,按照我们的JVM参数,如果分配一个超过1MB的大对象,比如说你创建一个大数组或者是大List之类的,就会直接进入老年代。

但是这种大对象我们假设在这个案例里是没有的,所以可以忽略不计。

此外就是Minor GC过后可能存活的对象超过200MB放不下Survivor了,或者是一下子占到超过Surviovr的50%,此时会有一些对象进入老年代中。

但是我们之前对新生代的JVM参数进行优化,就是为了避免这种情况,经过我们的测算,这种概率应该是很低的。

但是虽说是很低,也不能完全是是没有这种情况,比如某一次GC过后可能刚好机缘巧合有超过200MB对象,就会进入老年代里。

我们可以做一个假设,大概就是这个订单系统在大促期间,每隔5分钟会在Minor GC之后有一小批对象进入老年代,大概200MB左右的大小,如下图所示。


大促期间多久会触发一次Full GC?

接着我们来研究一下,那么按照Full GC的触发条件,多久会触发一次Full GC?

首先来看看,Full GC的触发条件目前我们学习到的有以下4种:

  • 没有打开“ -XX:HandlePromotionFailure”选项,结果老年代可用内存最多也就1G,新生代对象总大小最多可以有1.8G . 那么会导致每次Minor GC前一检查,都发现“老年代可用内存” < “新生代总对象大小”,这会导致每次Minor GC前都触发Full GC。

现在JDK 1.6以后的版本废弃了这个参数,其实只要满足下面第二个条件就可以直接触发Minor GC,不需要触发Full GC。

  • 每次Minor GC之前,都检查一下“老年代可用内存空间” < “历次Minor GC后升入老年代的平均对象大小”

其实按照我们目前设定的背景,要很多次Minor GC之后才可能有一两次碰巧会有200MB对象升入老年代,所以这个“历次Minor GC后升入老年代的平均对象大小”,基本是很小的。

  • 可能某次Minor GC后要升入老年代的对象有几百MB,但是老年代可用空间不足了
  • 设置了“-XX:CMSInitiatingOccupancyFaction”参数,比如设定值为92%,那么此时可能前面几个条件都没满足,但是刚好发现这个条件满足了,比如就是老年代空间使用超过92%了,此时就会自行触发Full GC

其实在真正的系统运行期间,可能会慢慢的有对象进入老年代,但是因为新生代我们优化过了内存分配,所以对象进入老年代的速度是很慢的。

所以很可能是在系统运行半小时~1小时之后,才会有接近 1GB的对象进入老年代。

此时可能会因为上述的条件234中任何一个满足了,就触发Full GC。

但是这三个条件一般都需要老年代近乎占满的时候,才有可能会触发。

大家可以思考一下,我们假设在大促期间,订单系统运行1小时之后,大促下单高峰期几乎都快过了,此时才可能会触发一次Full GC。

注意,这个推论很重要,因为按照大促开始10分钟就有50万订单来计算,其实大促开始后一堆用户等着下单剁手购物

那么1小时候就可能有两三百万订单了,这是一年难得罕见的节日大促才会有的,然后这个高峰期过后,基本订单系统访问压力就很小了,那么GC的问题几乎就更不算什么了。

所以经过新生代的优化,可以推算出,基本上大促高峰期内,也就可能1小时才1次Full GC,然后高峰期一过,随着订单系统慢慢运行,可能就要几个小时才有一次Full GC。


老年代GC的时候会发生“Concurrent Mode Failure”吗?

经过前面的推算,我们基本可知道,假设就是订单系统运行1小时之后,老年代大概有900MB的对象了,剩余可用空间仅仅只有100MB了,此时就会触发一次Full GC,如下图。

但是有一个很大的问题,就是CMS在垃圾回收的时候,尤其是并发清理期间,系统程序是可以并发运行的,所以此时老年代空闲空间仅剩100MB了

然后此时系统程序还在不停的创建对象,万一这个时候系统运行触发了某个条件,比如说有200MB对象要进入老年代,此时会如何?

如下图

这个时候就会触发“Concurrent Mode Failure”问题,因为此时老年代没有足够内存来放这200MB对象,此时就会导致立马进入Stop the World,然后切换CMS为Serial Old,直接禁止程序运行,然后单线程进行老年代垃圾回收,回收掉900MB对象过后,再让系统继续运行,如下图。

所以可以想一下,这种情况可能发生吗?

概率是挺小的,因为必须是CMS触发Full GC的时候,系统运行期间还让200MB对象进入老年代,这个概率其实本身就很小,但是理论上是有可能的。

大家此时需要思考一下,相对于这种小概率的事件而言,有必要去调整参数吗?

暂时看来是没有必要的,不需要针对小概率事件特意优化参数。

此时JVM参数如下:

“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M  -XX:PermSize=256M -XX:MaxPermSize=256M  -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92”

CMS垃圾回收之后进行内存碎片整理的频率应该多高?

接着来看最后一个问题,在CMS完成Full GC之后,一般需要执行内存碎片的整理,可以设置多少次Full GC之后执行一次内存碎片整理,但是我们有必要修改这些参数吗?

其实没必要,因为通过前面的分析,在大促高峰期,Full GC可能也就1小时执行一次,然后大促高峰期过去之后,就没那么多的订单了,此时可能几个小时才会有一次Full GC。

所以就保持默认的设置,每次Full GC之后都执行一次内存碎片整理就可以,目前JVM参数如下:

“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M  -XX:PermSize=256M -XX:MaxPermSize=256M  -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0”

其实从本文可以看到,Full GC优化的前提是Minor GC的优化,Minor GC的优化的前提是合理分配内存空间,合理分配内存空间的前提是对系统运行期间的内存使用模型进行预估。

其实对很多普通的Java系统而言,只要对系统运行期间的内存使用模型做好预估,然后分配好合理的内存空间,尽量让Minor GC之后的存活对象留在Survivor里不要去老年代,然后其余的GC参数不做太多优化,系统性能基本上就不会太差。


相关文章
|
24天前
|
Prometheus 监控 算法
CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究
本文介绍了CMS(Concurrent Mark-Sweep)垃圾回收器的工作原理、优缺点及常见问题,并通过具体案例分析了其优化策略。重点探讨了CMS的各个阶段,包括标记、并发清理和重标记
CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究
|
30天前
|
存储 监控 Java
JVM实战—8.如何分析jstat统计来定位GC
本文详细介绍了使用jstat、jmap和jhat等工具分析JVM运行状况的方法,以及如何合理优化JVM性能。内容涵盖新生代与老年代对象增长速率、Young GC和Full GC的触发频率及耗时等关键指标的分析。通过模拟BI系统和计算系统的案例,展示了如何根据实际场景调整JVM参数以减少FGC频率,提升系统性能。最后汇总了常见问题及其解决方案,帮助开发者更好地理解和优化JVM运行状态。
JVM实战—8.如何分析jstat统计来定位GC
|
29天前
|
缓存 监控 算法
JVM实战—10.MAT的使用和JVM优化总结
本文详细探讨了JVM内存管理与性能优化的关键问题。首先分析了线上大促活动引发的老年代内存泄漏及频繁FGC问题,通过MAT工具定位到本地缓存未正确处理的原因,并提出使用Ehcache等框架解决。接着讨论了百万级数据误处理导致的频繁FGC案例,深入剖析String.split()方法在特定JDK版本下的内存消耗问题,并给出多线程并发处理大数据量的优化建议。文章还总结了JVM运行原理、GC机制以及YGC和FGC的触发条件,明确了正常系统GC频率指标。最后提供了JVM性能优化的整体思路,包括新系统开发时的参数预估、压测后的调整策略以及线上系统的监控方法,同时列举了常见的FGC原因及对应解决方案。
165 79
JVM实战—10.MAT的使用和JVM优化总结
|
28天前
|
消息中间件 缓存 Java
JVM实战—11.OOM的原因和模拟以及案例
本文详细探讨了Java系统中内存溢出(OutOfMemory,简称OOM)问题的成因与解决方法。首先分析了线上系统因OOM挂掉的常见场景及处理思路,接着深入讲解了JVM中可能发生OOM的三大区域:Metaspace(类信息存储区)、栈内存(线程执行方法时使用)和堆内存(对象存储区)。针对每个区域,文章通过具体代码示例模拟了内存溢出的情况,如动态生成过多类导致Metaspace溢出、无限递归调用引发栈内存溢出以及高负载下堆内存不足等问题。最后结合实际案例,如大数据处理系统因Kafka故障未正确处理数据缓存而导致OOM,以及无限循环调用或未缓存动态代理类引发的问题,给出了预防和改进措施。
187 64
JVM实战—11.OOM的原因和模拟以及案例
|
30天前
|
SQL 缓存 监控
JVM实战—9.线上FGC的几种案例
本文详细探讨了JVM性能优化中的几个关键案例与问题。首先分析了如何优化每秒十万QPS的社交APP,通过增加Survivor区大小和优化内存碎片解决频繁Full GC的问题。接着讨论了垂直电商后台系统FGC的深度优化,定制JVM参数模板以降低GC频率。还探讨了不合理设置JVM参数导致频繁FGC的情况,并提出了解决方案。此外,针对线上系统每天数十次FGC的问题,定位到大对象是主要原因,并通过调整新生代大小等参数优化。同时,分析了电商大促活动中因System.gc()调用导致系统卡死的现象,建议禁用显式GC。
122 10
JVM实战—9.线上FGC的几种案例
|
25天前
|
SQL 前端开发 Java
JVM实战—13.OOM的生产案例
本文详细探讨了多种线上系统中引发OOM(内存溢出)问题的原因及排查方法。内容涵盖:1)每秒仅上百请求的系统因RPC超时时间设置过长导致QPS激增而OOM;2)Jetty服务器NIO机制因堆外内存管理不当引发内存溢出;3)微服务架构下RPC调用因类定义不一致导致超大byte[]数组占用内存;4)SQL语句缺少WHERE条件查询大量数据引发OOM;5)日志分析系统因堆内存不足与递归操作耗尽内存;6)类加载器过多导致内存使用过高被OS杀死进程;7)数据同步系统频繁OOM的排查与解决;8)总结JVM参数优化、GC问题定位及OOM分析方法。
JVM实战—13.OOM的生产案例
|
27天前
|
缓存 监控 Java
JVM实战—12.OOM的定位和解决
本文详细探讨了JVM内存管理中的常见问题及其解决方案,包括如何监控和报警系统的OOM异常、在内存溢出时自动Dump内存快照、解决Metaspace区域内存溢出、栈内存溢出(StackOverflowError)以及堆内存溢出(OutOfMemoryError: Java heap space)。针对每种情况,文章提供了具体的解决思路、示例代码、GC日志分析及内存快照分析方法。通过搭建系统监控体系、调整JVM参数和使用工具如MAT,可以有效定位和解决各类内存问题,优化系统性能并避免崩溃风险。
JVM实战—12.OOM的定位和解决
|
22天前
|
存储 监控 架构师
ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究
ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究
|
1月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
160 29
JVM简介—1.Java内存区域
|
3月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
717 166