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

简介: 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参数、减少对象产生、更换垃圾回收器、优化垃圾回收器的参数)等内容,希望对大家有所帮助。

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
存储 Java 开发者
浅析JVM方法解析、创建和链接
上一篇文章《你知道Java类是如何被加载的吗?》分析了HotSpot是如何加载Java类的,本文再来分析下Hotspot又是如何解析、创建和链接类方法的。
604 132
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
407 10
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
存储 IDE Java
实战优化公司线上系统JVM:从基础到高级
【11月更文挑战第28天】Java虚拟机(JVM)是Java语言的核心组件,它使得Java程序能够实现“一次编写,到处运行”的跨平台特性。在现代应用程序中,JVM的性能和稳定性直接影响到系统的整体表现。本文将深入探讨JVM的基础知识、基本特点、定义、发展历史、主要概念、调试工具、内存管理、垃圾回收、性能调优等方面,并提供一个实际的问题demo,使用IntelliJ IDEA工具进行调试演示。
293 0
|
监控 Java Spring
JVM如何监控某个方法的入参和相应结果?
JVM如何监控某个方法的入参和相应结果?
262 0
|
11月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
922 55
|
12月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
891 6
|
6月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
463 5