深入理解JVM - 实战老年代优化

简介: 深入理解JVM - 实战老年代优化

通过前面的文章可以了解到JVM优化中老年代的FULL GC对于系统以及垃圾收集器的行为有着十分大的影响,比如CMS并发标记或者回收撑不住的时候要暂停用户线程并且呼叫serrial收集器帮忙进行单线程的高效回收的动作,但是也伴随着"漫长"的stop world时间。


综上所述,老年代的优化是JVM优化的一个核心知识点,所以这一节就来讲解如何优化老年代的回收,尽量让对象在新生代回收而不是在老年代进行回收。


前文回顾



之前的文章我们对于JVM分代以及垃圾回收有了一个详细的了解,同时了解了jdk9之前主流的垃圾收集器ParNew收集器和CMS收集器,并且对于cms的细节进行了完整的讲述,这些内容都是十分基础但是又十分重要的知识点,这篇文章则根据前面的知识点,根据一个模拟的案例来讲解一下如何对于JVM老年代进行调优。


案例实战



案例实战会根据一个非常理想化的模拟场景,因为应用的性能实际上会有各种的影响,甚至代码的质量也会影响系统的运行性能,所以下面的模拟场景均为假想的情况,切勿过于认真的对待这个案例的各种参数。


一个电商系统的大致背景:


如果一个电商网站每天的访问量是20次/人,如果要上亿次的请求需要每天500万次的请求,同时如果这500万人按照10%的下单的标准,则是每天50万人会进行下单的操作,而下单操作按照2/8原则在4小时之内付款完成,那么此时的占用大概是50万/4小时 == 500000 / 14400,大概每秒也就 34个订单左右,这种情况下发现系统的影响并不会很大,老年代发生回收大概为几个小时一次,完全可以接受。


高并发的场景


但是如果在秒杀的场景,情况又不一样了,如果在一秒内来1000笔订单,该如何处理?我们假设如果是3台机器,则每台需要处理至少300条请求。


计算JVM消耗


根据上文模拟场景,假设每秒300个请求按照每个对象1KB来看,每一台机器要处理大概300KB的内存,把一个订单系统的处理对象放大10倍,则是3000KB,如果在算上其他的操作比如订单处理,则需要30000KB = 30MB的占用。

如果虚拟机栈每个占用1M,则几百个线程需要几百M的空间。如果是4核心8G的机器,则分4G给JVM,4G中分1G给虚拟机栈500M多M,方法区:256M,堆外内存给256M。同时开启内存担保机制(jdk6之后不需要制定参数)然后新生代和老年代各分配1.5G。

按照上面的换算,我们发现如果每秒都来30M对象,那么1200M左右的EDEN区域(8:1:1的比例大概是1200给EDEN),大概会留下200M左右的内存会进入SURVIOR区域,但是如果SURVIOR区域放不下则会进入老年代,根据之前的参数分配150M的Survior区域肯定是无法存放的,根据内存分配担保的机制,这些对象会分配到老年代。

按照这样的分配效率不到一分钟新生代就会塞满。大概8、9次minor gc就会导致full gc,也就是说 8、9分钟就会触发老年代回收,这个触发的概率就十分高了,这会严重导致系统卡顿并且出现用户线程的停顿现象。

但是如果Survior空间足够,那么此时回收进入到Survior空间之后,在下一次minor gc基本也为垃圾对象被回收了。


垃圾回收的优化


上面的案例优化也非常简单,在讲解最终的优化方案之前,我们按照下面的步骤进行分析:


检查Survior区域是否可以保证每次minor gc都可以全部进入


首先我们需要确认新生代的内存在垃圾回收之后是否都可以进入到Survior区域,很明显,根据案例Survior区域的大小为150M左右,而每次Minor GC之后存活对象通常为200M左右,这时候明显是无法存放下的,所以我们需要。


多大的对象应该进入老年代


之前说过一秒钟会产生30M对象,而Minor gc之后对象基本只剩下100M左右了,也就是说1分钟大概存活100M对象,那么平摊下来一秒大概产生1-2M的大对象。

所以一般情况下设置个1M的阈值就差不多了


对象经过多少年龄进入老年代


一般情况下默认的15就是一个不错的值。但是对于高并发的业务来说,大对象早点进入老年代反而是好事。因为survior存在一个控制值50%,累加对象大小超过Survior区域50%之后大于等于此年龄全部会进入老年代,所以有时候让新生代的对象提前进入老年代也是一种值得考虑的事情。


比如我们可以将进入老年代年龄的对象设置为7或者8。


指定垃圾收集器


注意一定要在参数里面指定垃圾收集器,这是十分重要的内容。


比如:-XX:+UseParNewGC
复制代码


最终优化参数结果


经过上面的一系列分析,我们可以确定根本问题出在了对象提前进入了老年代导致Survior区域成为摆设并且老年代的对象不断扩展,最终老年代塞满而导致频繁full gc,所以案例最后的优化参数如下:


网络异常,图片无法展示
|


老年代的内存要如何优化呢?


针对上面的案例,我们再分析几点内容:


老年代需要开启分配担保失败么?


我们看下如果没有开启分配担保失败会如何?首先如果没有开启,如果此时老年代的可用内存为400M,并且发现新生代总大小 < 老年代可用内存大小,每次Minor Gc都将会伴随着Full Gc,所以jdk6也关闭了这个参数并且这个参数也是默认开启的,绝大多数的情况下这个参数不用去管,默认都是要开启的。

如果老年代的总大小<新生代的大小,那么如果没有开启分配担保每一次请求都是会发生Full Gc的。


如果使用CMS收集器,是否需要改动92%的参数


从案例的角度来看,优化之后如果老年代到达900M说明此时订单系统已经运行很久了(几个小时),一般情况下秒杀早就结束了,此时进行Full GC也不会影响业务的处理和请求的处理。


进入老年代触发full Gc对系统影响可能性


如果某一次gc之后新生代存活对象大于200M,发现 Survior 区域放不下,此时老年代判断历次晋升平均大小,发现基本都是可以分配的,因为调整过后一般情况下只有几十M大小的对象进入,所以这样的概率还是比较小的,即使出现这种情况,此时系统也过去了很久了,高峰下单一般在前10分钟,如果前10分钟没有进行Fu'll Gc 而1小时之后进行了,这样也是不影响订单系统的运行的,因为此时的压力很小了.


Concurrent Mode fail 这种情况的有无影响


和前面说的分析一样,如果900M的老年代空间已经被占用满了,此时系统进程和垃圾回收线程同时进行,如果在并发整理的时候进入200M的对象,那么最坏的情况是触发失败导致stop world 并且serial 进行单线程的回收处理动作,但是需要考虑的是这种情况假设已经过去1个小时了,而此时的订单压力也小很多,Full Gc的影响也是可以承受的,在进行完这次Full GC之后,下一次可能就在几个小时之后的,这种情况虽然有可能发生,但是几率十分小。


内存碎片整理的概率有多大


还是和上面的情况一样,如果出现FULL GC也说明此时系统运行比较久了,一次FULL GC的间隔十分长的情况下每次FULL GC进行内存碎片整理的代价是可以接受的。


总结


下面根据这个案例总结一下如何思考优化的点

  1. 首先业务的对象都是生命周期十分短暂的对象,新生代的压力比老年代要大,所以适当缩小老年代空间是十分划算的
  2. 预测在高并发的场景下对象进入老年代的时机,如果对象经常“跨区”说明有一部分内容空间是浪费了,那就是Survior区域
  3. 对象在各分区需要大致多少的内存空间,比如每个线程需要占用多少的内存空间
  4. 对象的年龄判断是否需要改动,提前让对象进入老年代是好处还是坏处
  5. 关注收集器对于对象垃圾回收的影响,同时在启动的时候要强制使用某一垃圾收集器,因为不同的JDK版本默认的垃圾收集器是不一样的。
相关文章
|
5天前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
27 5
|
6月前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
102 1
|
5月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
50 0
|
2月前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
54 2
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
50 3
|
2月前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
44 1
|
2月前
|
算法 Java
JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
本文详细介绍了JVM中的GC算法,包括年轻代的复制算法和老年代的标记-整理算法。复制算法适用于年轻代,因其高效且能避免内存碎片;标记-整理算法则用于老年代,虽然效率较低,但能有效解决内存碎片问题。文章还解释了这两种算法的具体过程及其优缺点,并简要提及了其他GC算法。
 JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
|
2月前
|
监控 Java
Java的JVM如何优化?
Java的JVM如何优化?
60 3
|
5月前
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
116 1