前阵子写了一个RPC服务,测试的过程中,同时在测试的过程中反应间歇性超时比较严重,我的第一反应就是gc有问题,于是就观察了一阵子GC情况,发现这个服务的YGC时间有点不太正常,快的时候也就几十ms,慢的时候几十秒,总体上说就是快慢波动比较明显。
先说说这个服务,这个服务里面有个后台线程,每10分钟会去读一个大文件,然后将文件的数据缓存到本地,这个文件大概有100M不到,也就是说每10分钟,即使没有对外服务的情况下,也会有一次内存的加载过程,会产生一个内存波动。
为了查这个超时的问题,也恶补了一些JVM的相关知识,首先我的疑问是,YGC时间长会影响到服务超时?我之前一直以为YGC是不会stop the world的,只是copy。我试着在程序YGC的过程中,去访问这个服务,发现确实在长时间YGC的过程中,超时情况很严重,待YGC停止后,再访问服务,就很快响应了,所以事实摆在眼前,发现YGC的过程确实是有暂停服务的嫌疑,通过查看JVM参数,发现服务使用的CMS的GC方式,查阅相关的资料,得知CMS的YGC和并行GC的方式一样,stop-the-world + copy。确实是YGC时间过长导致服务超时比较严重,这样算是把超时问题的原因给定位了。
那究竟是什么原因导致YGC时间会很长呢,通过用jstat观察我发现一个问题,就是每次Eden区满的时候,一执行ygc survivor区几乎就直接100%了。我当时的感觉就是survivor区太小,eden区大?于是我试着调整了一下-XX:SurvivorRatio参数,默认这个值是8,也就是S0 :S1 :Eden之间的比例是1:1:8,我把这个比例改成了1:1:3,改完之后,通过一个晚上的观察,发现情况有所好转,但是这个问题还是存在,前几次YGC几乎都是很短时间就完成了,但是随着时间的积累,YGC时间过长的问题还是会出现。
94.56 0.00 53.09 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 58.91 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 66.68 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 72.50 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 76.38 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 82.21 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 88.68 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 92.56 3.44 20.33 56 2146.252 10 6.321 2152.574
94.56 0.00 100.00 3.44 20.33 57 2146.252 10 6.321 2152.574
94.56 54.25 100.00 3.44 20.33 57 2146.252 10 6.321 2152.574
S0 S1 E O P YGC YGCT FGC FGCT GCT
94.56 100.00 100.00 3.54 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 3.94 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 4.47 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 5.40 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 5.86 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 6.02 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 6.34 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 6.50 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 6.58 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 6.69 20.33 57 2146.252 10 6.321 2152.574
S0 S1 E O P YGC YGCT FGC FGCT GCT
94.56 100.00 100.00 6.79 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 7.09 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 7.34 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 7.76 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 8.53 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 8.79 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 9.49 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 10.34 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 11.23 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 12.18 20.33 57 2146.252 10 6.321 2152.574
S0 S1 E O P YGC YGCT FGC FGCT GCT
94.56 100.00 100.00 13.04 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 13.46 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 14.10 20.33 57 2146.252 10 6.321 2152.574
94.56 100.00 100.00 14.42 20.33 57 2146.252 10 6.321 2152.574
0.00 100.00 4.20 14.50 20.33 57 2171.829 10 6.321 2178.151
0.00 100.00 4.20 14.50 20.33 57 2171.829 10 6.321 2178.151
0.00 100.00 4.76 14.50 20.33 57 2171.829 10 6.321 2178.151
0.00 100.00 4.76 14.50 20.33 57 2171.829 10 6.321 2178.151
接着我继续观察,加上gc log观察gc时间情况,加上如下参数,先解释下,-XX:+PrintGCTimeStamps -XX:+PrintGCDetails 是打印出gc的发生时间和gc的详细日志,-XX:+PrintHeapAtGC 是打印gc前后的堆情况,-XX:+PrintGCApplicationStoppedTime 是打印每次gc的停顿时间。通过gc日志和jstat观察,发现只有survivor区中的一个快满的时候,假设现在快满的是S0,这时候发生YGC时,它是直接把GC的东西放入S1中,而S0里的内容并没有放入S1里,这样就出现了Jstat 状态里的S0,S1,和Eden区几乎都是100%的情况,当三者都满时,Eden里释放不了的东西就直接进Old区了,同时YGC也正常发生,这种情况下的GC停顿时间就特别长。这只是个现象,只能说在满足这个条件下时,YGC的时间就特别长。
通过查阅相关JVM的资料,发现正常情况下S0和S1只有一块空间是有内容的,但是我这里的情况看上去,S0和S1在GC的过程中都是满的,这是我的一个疑问?问题还是没有解决,但是冥冥之中有种暗示,就是再满足这个情况下的时候就会出现YGC时间过长的问题,真的是这样吗?我继续观察。
这次我想到了jconsole,曾经查一次gc的问题中,也用过它。其实用它只是为了更好的把gc的情况图形化,而且是实时的呈现。有时候数据往往看不出的端倪,放到图形里就能激发你的灵感,也许这就是为什么boss喜欢看报表吧。既然我找到了呈现问题的方式,那我就试着让JVM达到我想复现的YGC条件,我写了一个测试类,目的就是让它频繁调用这个服务,让其Eden区快速的涨起来,以触发YGC,在s0区域快满的时候,我开始执行我写的那个类,果然Eden区迅速的满了,但是情况并不是像我想象中的那样,YGC情况很快,YGC发生后,Eden区的内容往S1里转移,同时S0的内容也交换到了S1里,S0交换后也清0了,这个发现让我有了新的发现。我就想,难道是调用产生的新对象因为调用完毕,失去引用而直接回收了,进而虽然Eden区是满的,实际copy到survivor区的内存只有一部分?为了证实我的想法,我等待的下一次load数据,当load完数据后,我又调用我写的那个类方法,这次想象和我预期的一样,S1的数据交换到S0,Eden区也迅速释放出去了,YGC也进行的很快。看来问题已经有点眉目了。
接下来我试着jmap -histo {pid},看看堆中到底是些什么对象一直没回收,我发现每次YGC一直在增长的对象就是load文件里的小对象,我猜想这些对象因为是常驻内存的,所以回收起来肯定没有方法调用中产生的临时对象回收的快,所以我猜想每次在S0和S1中交换的对象大部分都是load文件后常驻内存的小对象。这时候又一个疑问产生了,为什么每次YGC这些对象都不被回收,不会有溢出吧,于是我试着用jconsole执行一次full gc,再jmap观察,发现小对象也回收了,S0和S1也被清除的干干净净,我的疑问这才算解决了,原来这种常驻内存的对象是不会轻易的被YGC给回收掉,不过我也没多想,因为full gc是可以回收掉它的,也就是说没有OOM的问题。
通过上面的发现,既然交换区的内存基本上都是本地缓存里的东西,那是不是只要保证交换区的空间一定可以装下Eden区里YGC回收不了的内存就可以保证YGC快速的完成?为了测试它的极限,我决定再调一次JVM的参数,我这时候把SurvivorRatio的比例放到了1,也就是说即使Eden区里的东西完全不被YGC回收,也可以保证Eden区迅速的被交换到survivor区。通过一个晚上的观察,发现这个结论和我预想的一样,YGC整体平稳,时间也比较理想,S0和S1始终有一块区域是空的。另外一个惊喜的发现是,一个晚上没有fullGC,我想这个原因应该是内存在交换空间里多次交换后,慢慢又被YGC给回收了的原因,导致每次YGC几乎就没有新的对象进入Old区,只要Old区不往上增长,那full gc自然就不怎么发生了。
这个问题到这里应该已经彻底解决了。最后附上一张jconsole的堆内存监控截图来结束这篇JVM调优日志。
相关推荐
《Monkey老师的JVM调优深度解析》 在Java开发领域,JVM(Java Virtual Machine)是每一个程序员都需要深入了解的关键组成部分。Monkey老师的JVM调优课程,无疑为我们提供了一个宝贵的平台,来深入探究JVM的工作原理...
《JVM调优实战解析》 在Java开发领域,JVM(Java Virtual Machine)是运行所有Java应用程序的基础,它的性能直接影响着程序的运行效率。因此,掌握JVM调优技术对于提升系统的稳定性和性能至关重要。本文将围绕"JVM...
### JVM调优攻略 #### 一、概述 《JVM调优攻略》是一份详尽的文档,旨在帮助开发者理解并掌握Java虚拟机(JVM)的优化技巧。本指南不仅适用于初学者,对于有一定基础的开发人员来说也同样具有很高的参考价值。文档中...
GC(Garbage Collection)调优和JVM调优是优化Java应用性能的关键环节,尤其是在处理大量数据或者高并发场景时显得尤为重要。 GC调优主要是对JVM的垃圾回收机制进行调整,以确保内存的有效利用和避免系统出现长时间...
JVM调优主要关注内存分配、垃圾收集策略以及类加载机制。常见的调优参数包括: - `-Xms` 和 `-Xmx`:设置堆内存的初始大小和最大大小。 - `-XX:NewRatio` 和 `-XX:SurvivorRatio`:控制新生代和Survivor区的比例。 ...
《深入解析JVM调优与监控》 在Java开发领域,JVM(Java Virtual Machine)是运行Java程序的核心,它的性能直接影响着应用的效率和稳定性。JVM调优是优化Java应用程序性能的关键环节,而"jvm-monitor"则提供了一种...
【JVM调优总结】 Java虚拟机(JVM)是Java程序运行的基础,它负责解析字节码并将其转换为机器可执行的指令。JVM调优是优化Java应用程序性能的关键步骤,尤其对于大型分布式系统而言,良好的JVM配置可以显著提高系统...
【JVM调优视频理论及工具】主要涵盖了Java虚拟机(JVM)的优化实践与相关的分析工具。在Java开发中,JVM调优是提升应用程序性能的关键环节,尤其是在高并发、大数据处理等场景下,良好的JVM配置能显著提高系统效率。...
《Java问题定位技术(书签版)—JVM调优》这本书可能涵盖了以上这些内容,通过深入学习,开发者可以掌握更高级的故障排查技巧,比如使用`jmap`、`jhat`等工具进行堆内存分析,或者利用`jfr`进行飞行记录,获取详细的...
JVM调优的目标是优化程序运行效率,减少不必要的系统资源消耗,特别是减少全GC(Full GC)的发生,因为Full GC会导致应用短暂停滞,影响用户体验。 首先,了解JVM的内存结构至关重要。Java内存主要分为堆内存(Heap...
Java JVM(Java虚拟机)内存分配与调优是Java开发者必须掌握的重要技能,它涉及到程序的性能优化和稳定性。在Java应用中,JVM扮演着至关重要的角色,它负责解析字节码、管理内存以及执行线程等。本文将深入探讨JVM...
### JVM调优基础知识点 #### 一、JVM基础知识 JVM(Java Virtual Machine)是运行Java程序的基础,它能够执行字节码(Bytecode),并提供了一个与平台无关的运行环境。对于JVM的基本理解有助于更好地进行调优工作...
JVM调优涉及多个方面,包括内存配置、垃圾收集器选择、线程设置、类加载机制优化等。这里我们将详细探讨这些关键知识点,并结合提供的"生产环境jvm调优的实例代码-jvm.zip"中的内容进行深入讲解。 1. **内存配置**...
《深入解析Tomcat JVM调优》 在Java应用服务器领域,Tomcat因其轻量级、高效和开源的特点,被广泛应用于各种Web应用的部署。然而,为了确保应用的高性能和稳定性,对Tomcat的JVM进行优化是必不可少的环节。本文将...
在这个全面理解JVM并掌握常规JVM调优的教程中,我们将深入探讨JVM的工作原理、内存模型、垃圾收集机制、类加载过程以及如何进行性能优化。 一、JVM工作原理 JVM的运行过程包括编译、加载、验证、解析、初始化、执行...
JVM调优是优化Java应用程序性能的重要环节,通过对JVM参数的调整,可以有效地提升程序运行效率,减少内存消耗,提高响应速度。 深入理解JVM涉及到以下几个核心领域: 1. **内存管理**:JVM内存分为堆内存、栈内存...
### JVM调优与内存管理详解 #### 调优目标及基本概念 在进行JVM调优时,首要目标是确保应用程序能够高效稳定地运行。这通常涉及到几个关键指标:吞吐量(Throughput)、延迟(Latency)等。其中,**吞吐量**指的是单位...
### jvm调优建议文档知识点解析 #### 一、JVM基本概念及组成部分 - **JVM内存区域划分**:JVM内存分为新生代、老年代以及元空间(Metaspace)三大区域。其中,新生代负责存放新创建的对象,经过多次垃圾回收后存活的...
本文将深入探讨Spring微服务框架及其与JVM调优、数据监控和并发控制的关联。 首先,Spring框架是一个全面的Java企业级应用开发平台,它简化了开发过程并提供了丰富的功能,包括依赖注入(DI)、面向切面编程(AOP)...