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参数不做太多优化,系统性能基本上就不会太差。


相关文章
|
3月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
91 0
|
3月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
3月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
4月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
4月前
|
监控 Java 编译器
Java虚拟机调优指南####
本文深入探讨了Java虚拟机(JVM)调优的精髓,从内存管理、垃圾回收到性能监控等多个维度出发,为开发者提供了一系列实用的调优策略。通过优化配置与参数调整,旨在帮助读者提升Java应用的运行效率和稳定性,确保其在高并发、大数据量场景下依然能够保持高效运作。 ####
65 1
|
4月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
5天前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
JVM简介—1.Java内存区域
|
2月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
654 166
|
2天前
|
消息中间件 Java 应用服务中间件
JVM实战—2.JVM内存设置与对象分配流转
本文详细介绍了JVM内存管理的相关知识,包括:JVM内存划分原理、对象分配与流转、线上系统JVM内存设置、JVM参数优化、问题汇总。
JVM实战—2.JVM内存设置与对象分配流转
|
4天前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略