jvm性能调优实战 - 48无限循环调用和没有缓存的动态代理引起的OOM

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: jvm性能调优实战 - 48无限循环调用和没有缓存的动态代理引起的OOM

Case 1 递归调用

当时有一个非常重要的系统,我们设计了一个链路监控机制,也就是会在一个比较核心的链路节点,写一些重要的日志到Elasticsearch集群里去,事后会基于ELK进行核心链路日志的一些分析,如下图所示。

同时我们对这个机制做了规定,如果在某个节点写日志时发生了某些异常,此时也必须将这个链路节点的异常写入ES集群里去,因为我们在分析的时候,需要知道系统运行到这里有一个异常。

不知道大家看了上面的代码是作何感想?当时这个同学居然在log()方法中一旦ES集群出现故障的时候再次调用了自己,继续尝试将日志写入ES集群。

因此在线上系统中,有一次ES集群短暂故障了一会儿,结果直接就导致log()方法中写ES集群每次都是失败的,都会抛异常。

而一旦抛异常进入了catch语句中,就会再次重新回过头来调用log()方法。

然后log()方法再次写ES集群发现不行,继续抛异常进入catch中,再次循环调用自己。

线上系统本来在ES集群故障的时候不该有什么问题的,因为核心业务逻辑都是可以运行的,最多不过就是无法把核心日志写入ES集群罢了。

但是因为这个bug,导致在ES故障时,所有系统全部在写日志的时候,陷入了一个无限循环调用log()方法的困境中。

之前演示过,一旦无限循环调用方法自己,一定会在一定时间导致线程的栈内存溢出的,此时直接会导致JVM进程的崩溃

系统居然因为这么一个小问题崩溃了!这就是一次非常真实的线上案例。

后来针对此类问题,我们都是通过严格的持续集成+严格的Code Review标准来避免的


Case2 没有缓存的动态代理

简单来说,想要实现一个动态代理机制,也就是说在系统运行的时候,针对已有的某个类,生成一个动态代理类,也就是动态生成类,然后对那个类的一些方法调用做一些额外的处理。

当时大概的一个伪代码

不知道大家发现类似这种代码里的一个问题没有?比如你用CGLIB的Enhancer针对某个类动态生成了一个子类,这个子类你完全可以缓存起来,下次直接用这个已经生成好的子类来创建对象就可以了

类似下面这样:

其实这个类只要生成一次就可以了,下次来直接用这个动态生成的类创建一个对象就可以了。

但是当时那个工程师没有缓存这个动态生成的类,就是每次调用方法都生成一个类,这就闯祸了。

有一次线上系统负载很高的时候,因为这个框架直接导致瞬间创建了一大堆的类,塞满了Metaspace区域无法回收,进而导致Metaspace区域直接内存溢出,系统也崩溃了,这也是一个很大的问题。

后来对于这类问题,是严格要求每次上线必须走严格的自动化压力测试,通过高并发压力下系统是否正常运行支撑24小时,来判断是否可以上线。

这样类似于这类代码在上线之前就会被压力测试露出马脚,因为压力一大,瞬间会引发这个问题。


小结

我们带着大家感受了一下各种内存溢出发生的场景,同时给出了几个真实的线上生产案例是如何导致各个内存区域溢出的

相信大家对内存溢出这个问题,有了一个更加深刻的理解。

接下来我们会带着大家一起来学习如何对线上的OOM进行监控,同时在OOM时如何让JVM自动保留现场,同时结合几个案例和工具学习,发生OOM之后如何快速排查和定位到底代码哪里出现了OOM,以及如何进行解决。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
存储 监控 Java
JVM实战—8.如何分析jstat统计来定位GC
本文详细介绍了使用jstat、jmap和jhat等工具分析JVM运行状况的方法,以及如何合理优化JVM性能。内容涵盖新生代与老年代对象增长速率、Young GC和Full GC的触发频率及耗时等关键指标的分析。通过模拟BI系统和计算系统的案例,展示了如何根据实际场景调整JVM参数以减少FGC频率,提升系统性能。最后汇总了常见问题及其解决方案,帮助开发者更好地理解和优化JVM运行状态。
JVM实战—8.如何分析jstat统计来定位GC
|
3月前
|
缓存 监控 算法
JVM实战—10.MAT的使用和JVM优化总结
本文详细探讨了JVM内存管理与性能优化的关键问题。首先分析了线上大促活动引发的老年代内存泄漏及频繁FGC问题,通过MAT工具定位到本地缓存未正确处理的原因,并提出使用Ehcache等框架解决。接着讨论了百万级数据误处理导致的频繁FGC案例,深入剖析String.split()方法在特定JDK版本下的内存消耗问题,并给出多线程并发处理大数据量的优化建议。文章还总结了JVM运行原理、GC机制以及YGC和FGC的触发条件,明确了正常系统GC频率指标。最后提供了JVM性能优化的整体思路,包括新系统开发时的参数预估、压测后的调整策略以及线上系统的监控方法,同时列举了常见的FGC原因及对应解决方案。
215 79
JVM实战—10.MAT的使用和JVM优化总结
|
3月前
|
消息中间件 缓存 Java
JVM实战—11.OOM的原因和模拟以及案例
本文详细探讨了Java系统中内存溢出(OutOfMemory,简称OOM)问题的成因与解决方法。首先分析了线上系统因OOM挂掉的常见场景及处理思路,接着深入讲解了JVM中可能发生OOM的三大区域:Metaspace(类信息存储区)、栈内存(线程执行方法时使用)和堆内存(对象存储区)。针对每个区域,文章通过具体代码示例模拟了内存溢出的情况,如动态生成过多类导致Metaspace溢出、无限递归调用引发栈内存溢出以及高负载下堆内存不足等问题。最后结合实际案例,如大数据处理系统因Kafka故障未正确处理数据缓存而导致OOM,以及无限循环调用或未缓存动态代理类引发的问题,给出了预防和改进措施。
284 64
JVM实战—11.OOM的原因和模拟以及案例
|
3月前
|
存储 监控 Java
JVM实战—7.如何模拟GC场景并阅读GC日志
本文主要介绍了:如何动手模拟出频繁Young GC的场景、JVM的Young GC日志应该怎么看、编写代码模拟动态年龄判定规则进入老年代、编写代码模拟S区放不下部分进入老年代、JVM的Full GC日志应该怎么看。
126 16
JVM实战—7.如何模拟GC场景并阅读GC日志
|
3月前
|
消息中间件 存储 算法
JVM实战—6.频繁YGC和频繁FGC的后果
本文详细探讨了JVM中的GC机制及其优化策略,涵盖Young GC、Old GC和Full GC的触发条件与影响。首先分析了JVM GC可能导致系统卡顿的问题,特别是大内存机器上的YGC性能瓶颈,并通过G1垃圾回收器解决。接着通过实际案例展示了频繁FGC的成因及优化方法,如调整新生代与老年代内存比例或使用大内存机器。最后总结了不同GC算法的适用场景及对象生命周期特点,为JVM性能调优提供了实用指导。
241 15
JVM实战—6.频繁YGC和频繁FGC的后果
|
3月前
|
SQL 缓存 监控
JVM实战—9.线上FGC的几种案例
本文详细探讨了JVM性能优化中的几个关键案例与问题。首先分析了如何优化每秒十万QPS的社交APP,通过增加Survivor区大小和优化内存碎片解决频繁Full GC的问题。接着讨论了垂直电商后台系统FGC的深度优化,定制JVM参数模板以降低GC频率。还探讨了不合理设置JVM参数导致频繁FGC的情况,并提出了解决方案。此外,针对线上系统每天数十次FGC的问题,定位到大对象是主要原因,并通过调整新生代大小等参数优化。同时,分析了电商大促活动中因System.gc()调用导致系统卡死的现象,建议禁用显式GC。
219 10
JVM实战—9.线上FGC的几种案例
|
3月前
|
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的生产案例
|
3月前
|
缓存 算法 Java
JVM实战—4.JVM垃圾回收器的原理和调优
本文详细探讨了JVM垃圾回收机制,包括新生代ParNew和老年代CMS垃圾回收器的工作原理与优化方法。内容涵盖ParNew的多线程特性、默认线程数设置及适用场景,CMS的四个阶段(初始标记、并发标记、重新标记、并发清理)及其性能分析,以及如何通过合理分配内存区域、调整参数(如-XX:SurvivorRatio、-XX:MaxTenuringThreshold等)来优化垃圾回收。此外,还结合电商大促案例,分析了系统高峰期的内存使用模型,并总结了YGC和FGC的触发条件与优化策略。最后,针对常见问题进行了汇总解答,强调了基于系统运行模型进行JVM参数调优的重要性。
152 10
JVM实战—4.JVM垃圾回收器的原理和调优
|
3月前
|
缓存 监控 Java
JVM实战—12.OOM的定位和解决
本文详细探讨了JVM内存管理中的常见问题及其解决方案,包括如何监控和报警系统的OOM异常、在内存溢出时自动Dump内存快照、解决Metaspace区域内存溢出、栈内存溢出(StackOverflowError)以及堆内存溢出(OutOfMemoryError: Java heap space)。针对每种情况,文章提供了具体的解决思路、示例代码、GC日志分析及内存快照分析方法。通过搭建系统监控体系、调整JVM参数和使用工具如MAT,可以有效定位和解决各类内存问题,优化系统性能并避免崩溃风险。
JVM实战—12.OOM的定位和解决
|
3月前
|
消息中间件 算法 Java
JVM实战—5.G1垃圾回收器的原理和调优
本文详细解析了G1垃圾回收器的工作原理及其优化方法。首先介绍了G1通过将堆内存划分为多个Region实现分代回收,有效减少停顿时间,并可通过参数设置控制GC停顿时长。接着分析了G1相较于传统GC的优势,如停顿时间可控、大对象不进入老年代等。还探讨了如何合理设置G1参数以优化性能,包括调整新生代与老年代比例、控制GC频率及避免Full GC。最后结合实际案例说明了G1在大内存场景和对延迟敏感业务中的应用价值,同时解答了关于内存碎片、Region划分对性能影响等问题。