JVM工作原理与实战(三十四):解决GC问题的方法

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了常见的垃圾回收(GC)模式、解决GC问题的方法(优化基础JVM参数、减少对象产生、更换垃圾回收器、优化垃圾回收器的参数)等内容。

一、常见的垃圾回收(GC)模式

正常情况:在正常的情况下,内存使用呈现锯齿状,对象创建后内存上升,一旦发生垃圾回收,内存下降到底部,且每次下降后的内存大小较为接近。这种情况下,存活的对象较少,垃圾回收器能够有效地释放不再使用的对象所占用的内存。

image.gif

缓存对象过多:如果程序中保存了大量的缓存对象,那么内存使用也会呈现锯齿状。与正常情况不同的是,每次垃圾回收后内存下降的位置较高,且较为接近。这可能是由于缓存对象无法被垃圾回收器识别为无用对象,从而导致内存无法释放。针对这种情况,可以使用诸如MAT(Memory Analyzer Tool)或HeapHero等工具来分析内存占用情况,找出造成内存泄漏的对象。

image.gif

内存泄漏:如果程序存在内存泄漏,内存使用也会呈现锯齿状。与正常情况不同的是,每次垃圾回收后内存下降的位置越来越高,直到垃圾回收器无法释放更多内存,导致对象无法分配,从而产生OutOfMemoryError错误。这可能是由于某些对象持有对其他对象的引用,但这些引用在程序中不再需要,从而导致内存泄漏。同样可以使用MAT或HeapHero等工具来分析是哪些对象产生了内存泄漏。

image.gif

持续的FullGC:如果程序在某段时间内产生了多次Full GC,并且CPU使用率同时飙高,用户请求基本无法处理,那么可能是由于在该时间范围内请求量激增,程序开始生成更多对象。同时,垃圾收集器无法跟上对象创建速率,导致持续地进行Full GC。这种情况下,需要优化程序的内存管理策略,以减少Full GC的频率。

image.gif

元空间不足导致的FullGC:如果堆内存的大小并不是特别大,但持续发生Full GC,那么可能是由于元空间大小不足导致的。元空间是Java 8引入的一个概念,用于存储类的元数据。如果元空间大小不足,垃圾回收器将无法回收元空间的数据,从而导致Full GC的发生。在这种情况下,可以尝试增加元空间的大小或者优化程序的类加载策略。

image.gif

二、解决GC问题的方法

解决垃圾回收(GC)问题的专业方法如下:

  1. 优化基础JVM参数:基础JVM参数的配置对于垃圾回收性能至关重要。不当的参数设置可能导致频繁的Full GC,影响应用程序性能。通过深入了解和优化这些参数,可以减少Full GC的发生,提高GC效率。
  2. 减少对象产生:在大多数情况下,Full GC的发生是由于对象产生速度过快,导致内存迅速消耗。通过优化代码和数据结构,减少不必要的对象创建,可以有效地缓解GC压力,降低Full GC的频率。
  3. 更换垃圾回收器:不同的垃圾回收器适用于不同的业务场景。选择适合当前应用的垃圾回收器可以降低GC延迟,提高吞吐量,从而提升整体性能。在进行垃圾回收器的选择时,需要充分评估和测试不同回收器的性能表现,确保其满足业务需求。
  4. 优化垃圾回收器参数:垃圾回收器的参数配置对其性能有着重要影响。通过调整这些参数,可以在一定程度上提高GC效率,减少Full GC的发生。然而,优化垃圾回收器参数需要深入了解垃圾回收机制和具体垃圾回收器的行为特性,因此需要谨慎操作,并进行充分的测试和验证。

解决GC问题的方法中,前三种是比较推荐的方法,它们有助于从根本上改善GC性能。而第四种方法仅在前三种无法解决问题时作为备选方案考虑。在采取任何优化措施之前,建议进行充分的性能测试和分析,以确保所选方案的有效性和适用性。

1.优化基础JVM参数

在优化Java应用程序的垃圾回收(GC)性能时,参数调整是关键。

参数1:

  • -Xmx:此参数用于设置Java堆的最大内存大小。在服务器或容器环境中,除了堆内存外,还需要考虑元空间、操作系统和其他软件的内存占用。因此,在计算可用内存时,需要将这些资源排除在外。
  • -Xms:此参数用于设置Java堆的初始内存大小。建议将其设置为与-Xmx相同的值,以实现更好的运行时性能、避免可用性问题以及加快启动速度。

合理的设置方式是根据最大并发量估算服务器配置,然后根据服务器配置计算最大堆内存的值。

image.gif

案例:

服务器内存为4GB,操作系统、元空间和其他软件占用1.5GB,-Xmx可以设置为2GB。

参数2:

  • -XX:MaxMetaspaceSize:此参数用于设置元空间的最大内存大小。默认值通常较大,但如果出现元空间内存泄漏,可能会导致操作系统可用内存不可控。建议根据测试情况设置合理的最大值,一般可设置为256MB。
  • -XX:MetaspaceSize:此参数用于设置阈值,一旦达到该阈值,将会触发FULL GC。如果设置为与MaxMetaspaceSize相同的值,则不会触发FULL GC,但对象也无法回收。

image.gif

参数3:

  • -Xss:此参数用于设置每个线程的虚拟机栈大小。如果不指定栈大小,JVM将创建一个具有默认大小的栈。根据操作系统和计算机体系结构的不同,默认大小可能会有所不同。如果不需要使用大量栈内存,可以将其调小以节省内存空间。合理的值范围在256KB到1MB之间。

参数4(不建议手动设置的参数)

  • -Xmn:此参数用于设置年轻代的大小。年轻代是堆内存的一部分,用于存储新创建的对象。默认值为整个堆的1/3。根据峰值流量计算最大年轻代大小可以获得更好的性能,但实际场景中的响应时间、创建对象的大小和定时任务等因素可能会影响该值的准确性。建议在设置此值时要进行大量测试。注意,在使用G1垃圾回收器时,不建议手动设置此值,因为G1会动态调整年轻代的大小。

image.gif

  • -XX:SurvivorRatio:此参数用于设置伊甸园区和幸存者区的大小比例。默认值为8。
  • -XX:MaxTenuringThreshold:此参数用于设置对象晋升到老年代的最大年龄阈值。JVM具有动态年龄判断机制,如果年龄大于该阈值,对象将进入老年代。此外,JVM还有动态年龄判断机制:将年龄从小到大的对象占据的空间加起来,如果大于幸存者区域的50%,然后把等于或大于该年龄的对象放入老年代。

image.gif

其他参数:

  • -XX:+DisableExplicitGC:此参数用于禁止在代码中使用System.gc()方法。System.gc()可能会导致FULL GC,因此在代码中应尽量避免使用它。使用DisableExplicitGC参数可以禁止使用System.gc()方法调用。
  • -XX:+HeapDumpOnOutOfMemoryError:当发生OutOfMemoryError错误时,此参数会自动生成hprof内存快照文件。可以通过指定-XX:HeapDumpPath=<path>参数来指定hprof文件的输出路径。
  • 打印GC日志:根据所使用的JDK版本,可以使用不同的参数组合来打印GC日志的详细信息。对于JDK 8及之前版本,可以使用-XX:+PrintGCDetails、-XX:+PrintGCDateStamps和-Xloggc:文件路径等参数来打印GC日志。对于JDK 9及之后版本,可以使用-Xlog:gc*:file=文件路径等参数来打印GC日志。这些日志可以用于分析和诊断GC性能问题。
-Xms1g
-Xmx1g
-Xss256k
-XX:MaxMetaspaceSize=512m 
-XX:+DisableExplicitGC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/logs/my-service.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:文件路径
# JDK9及之后gc日志输出修改为 -Xlog:gc*:file=文件名

image.gif

2.更换垃圾回收器

垃圾回收器通过自动检测和回收不再被引用的对象,以释放内存空间,避免内存泄漏。为了实现这一目标,垃圾回收器采用了一系列算法来识别和回收无用对象。主要的垃圾回收器包括Serial垃圾回收器、SerialOld垃圾回收器、ParNew垃圾回收器、CMS垃圾回收器、Parallel Scavenge垃圾回收器、Parallel Old垃圾回收器、G1垃圾回收器。

3.优化垃圾回收器的参数

优化垃圾回收器的参数是提高Java应用程序性能的关键步骤之一。尽管这部分的优化效果可能并不显著,但在其他手动优化手段无效时,仍然值得考虑。

以CMS垃圾回收器的并发模式失败现象为例,这是由于CMS的垃圾清理线程和用户线程是并行运行的。在并发清理过程中,如果老年代的空间不足以容纳新晋升的对象,就会发生并发模式失败。这种情况可能导致Java虚拟机使用Serial Old单线程进行FULL GC回收老年代,从而引发长时间的停顿。

image.gif

为了解决这个问题,可以考虑以下几个解决方案:

  1. 减少对象的产生以及对象的晋升:通过优化代码和数据结构,减少不必要的对象创建和缩短对象的生命周期,可以降低老年代的占用空间,从而减少并发模式失败的可能性。
  2. 增加堆内存大小:通过增加堆内存的大小,为老年代提供更多的空间,从而降低并发模式失败的风险。但是,需要注意的是,增加堆内存大小并不一定能够解决所有性能问题,还需要根据实际情况进行权衡和测试。
  3. 优化垃圾回收器的参数:通过调整垃圾回收器的参数,可以更好地控制垃圾回收的行为和时机。例如,-XX:CMSInitiatingOccupancyFraction参数用于设置老年代空间到达一定比例时自动触发CMS垃圾回收的阈值。通过合理设置这个参数,可以提前进行老年代的垃圾回收,从而减少其大小,降低并发模式失败的风险。在JDK 8中,默认情况下该参数值为-1,可以通过开启-XX:+UseCMSInitiatingOccupancyOnly参数来使设置生效。

需要注意的是,垃圾回收器的参数优化是一个复杂的过程,需要深入理解Java虚拟机的内存模型和垃圾回收机制。在进行参数调整时,建议先进行性能测试和分析,以确定最佳的参数配置。同时,还需要注意监控和跟踪应用程序的性能指标,以便及时发现和解决潜在的性能问题。


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了常见的垃圾回收(GC)模式、解决GC问题的方法(优化基础JVM参数、减少对象产生、更换垃圾回收器、优化垃圾回收器的参数)等内容,希望对大家有所帮助。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7天前
|
存储 Java PHP
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
25 0
|
8天前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
2月前
|
缓存 监控 算法
吃透 JVM 诊断方法与工具使用
【8月更文挑战第4天】深入了解并掌握JVM诊断需把握几大要点:1) 熟悉JVM内存模型,如堆、栈及方法区;2) 掌握垃圾回收机制与算法;3) 运用工具如`jps`(查看Java进程)、`jstat`(监控运行状态)、`jmap`(生成堆快照)、`jhat`(分析堆快照)、`jstack`(检查线程栈); 4) 利用专业工具如Eclipse Memory Analyzer分析堆转储文件查找内存泄漏; 5) 动态监控与调整JVM参数; 6) 结合日志分析性能瓶颈。通过实战案例加深理解,有效应对JVM性能问题。
|
2月前
|
算法 Java 应用服务中间件
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
|
2月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
2月前
|
监控 Java Android开发
吃透 JVM 诊断方法与工具使用
【8月更文挑战第3天】要精通JVM诊断,需掌握关键监控指标如内存(堆/非堆)、CPU使用及线程状态;熟悉工具如`jstat`(监控状态)、`jmap`(堆转储)、`jstack`(线程堆栈);并能利用Eclipse Memory Analyzer (MAT)分析堆转储找内存泄漏;同时理解GC日志以优化垃圾回收行为;通过实践案例加深理解。
|
2月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
2天前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
|
2天前
|
Java API 对象存储
JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
本文详细解析了JVM类加载过程的关键步骤,包括加载验证、准备、解析和初始化等阶段,并介绍了元数据区、程序计数器、虚拟机栈、堆内存及本地方法栈的作用。通过本文,读者可以深入了解JVM的工作原理,理解类加载器的类型及其机制,并掌握类加载过程中各阶段的具体操作。
|
7天前
|
存储 Java Linux
【JVM】JVM执行流程和内存区域划分
【JVM】JVM执行流程和内存区域划分
28 1