jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化

简介: jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化

案例

先简单说一下业务背景:一次我们线上推了一个大促销活动, 系统一般在这个时候压力会比平时大好几倍。

但是因为从系统的整体设计角度而言,其实给的一些数据库、缓存和机器的资源都是足够的,所以通常而言不该有什么问题。

但是那次大促活动开始之后,直接导致线上一个系统的CPU使用率飙升,而且因为CPU使用率太高,导致系统几乎陷入卡死的状态,无法处理任何请求!

在重启系统之后,会好一段时间,但是很快又立马发现机器的CPU使用率飙升,继续导致系统卡死!


初步排查CPU负载过高的原因

这里给大家说一下线上系统的机器CPU负载过高的两个常见的场景。

  • 第一个场景,是你自己在系统里创建了大量的线程,这些线程同时并发运行,而且工作负载都很重,过多的线程同时并发运行就会导致你的机器CPU负载过高。
  • 第二个场景,就是你的机器上运行的JVM在执行频繁的Full GC,Full GC是非常耗费CPU资源的,他是一个非常重负载的过程

所以一旦你的JVM有频繁的Full GC,带来的一个明显的感觉,一个是系统可能时不时会卡死,因此Full GC会带来一定的“Stop the World”问题,一个是机器的CPU负载很高。

所以一旦知道CPU负载过高的两个原因,就很容易进行排查了。

大家完全可以使用排除法来做,首先看一下JVM Full GC的频率,通过jstat也好,或者是监控平台也好,很容易看到现在Full GC的频率。如果Full GC频率过高,那么就是Full GC引起的CPU负载过高。

那么如果JVM GC频率很正常呢?那就肯定是你的系统创建了过多线程在并发执行负载很重的任务了!

所以当时我们直接通过监控平台就可以看到,JVM的Full GC频率突然变得极为频繁,几乎是每分钟都有一次Full GC。

大家都知道,每分钟一次Full GC,一次至少耗时几百毫秒,这个系统性能绝对很糟糕,而且对机器的CPU负载也是很高的!

既然发现了频繁Full GC了,那肯定就不用去怀疑是系统自己创建过多线程了!


初步排查频繁Full GC的问题

大家通过之前大量的案例和文章已经初步可以得到结论,如果有频繁Full GC的问题,一般可能性有三个:

  • 内存分配不合理,导致对象频繁进入老年代,进而引发频繁的Full GC;
  • 存在内存泄漏等问题,就是内存里驻留了大量的对象塞满了老年代,导致稍微有一些对象进入老年代就会引发Full GC;
  • 永久代里的类太多,触发了Full GC

当然还有之前案例说过,如果上述三个原因都不存在,但是还是有频繁Full GC,也许就是工程师错误的执行“System.gc()”导致的

但是这个一般很少见,而且之前讲过,JVM参数中可以禁止这种显式触发的GC。

所以一般排查频繁Full GC,核心的利器当然是jstat了 。

当时我们用jstat分析了一下线上系统的情况,发现并不存在内存分配不合理,对象频繁进入老年代的问题,而且永久代的内存使用也很正常,所以上述三个原因中的两个就被排除掉了。

那么我们来考虑最后一个原因:老年代里是不是驻留了大量的对象给塞满了?

对,当时系统就是这个问题!

我们明显发现老年代驻留了大量的对象,几乎快塞满了,所以年轻代稍微有一些对象进入老年代,很容易就会触发Full GC!而且Full GC之后还不会回收掉老年代里大量的对象,只是回收一小部分而已!

所以很明显老年代里驻留了大量的本不应该存在的对象,才导致频繁触发Full GC的。接下来就是要想办法找到这些对象了

之前我们介绍过jmap+jhat的组合来分析内存里的大对象,今天我们介绍另外一个常用的强有力的工具,MAT。

因为jhat适合快速的去分析一下内存快照,但是功能上不是太强大,所以一般其实常用的比较强大的内存分析工具,就是MAT。


对线上系统导出一份内存快照

既然都发现线上系统的老年代中驻留了过多的对象的问题,那么肯定要知道这些对象是谁!

所以先用jmap命令导出一份线上系统的内存快照即可:

jmap -dump:format=b,file=文件名 [服务进程ID]

拿到了内存快照之后,其实就是一份文件,接着就可以用jhat、MAT之类的工具来分析内存了。


MAT是如何使用

下载地址:

https://www.eclipse.org/mat/downloads.php

下载好MAT后,在他的安装目录里,可以看到一个文件名字叫做:MemoryAnalyzer.ini

这个文件里的内容类似如下所示:

-startup
../Eclipse/plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
--launcher.library
../Eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.700.v20180518-1200
-vmargs
-Xmx1024m
-Dorg.eclipse.swt.internal.carbon.smallFonts
-XstartOnFirstThread

大家务必要记得,如果dump出来的内存快照很大,比如有几个G,你务必在启动MAT之前,先在这个配置文件里给MAT本身设置一下堆内存大小,比如设置为4个G,或者8个G,他这里默认-Xmx1024m是1G。

接着大家直接启动MAT即可,启动之后看到的界面中有一个选型是:Open a Heap Dump,就是打开一个内存快照的意思,选择他,然后选择本地的一个内存快照文件即可。


基于MAT来进行内存泄漏分析

使用MAT打开一个内存快照之后,在MAT上有一个工具栏,里面有一个按钮,他的英文是:Leak Suspects,就是内存泄漏的分析。

接着MAT会分析你的内存快照,尝试找出来导致内存泄漏的一批对象。

这时明显可以看到他会显示给你一个大的饼图,这里就会提示你说,哪些对象占用内存过大。

这个时候直接会看到某种自己系统创建的对象占用量过大,这种对象的实例多达数十万个,占用了老年代一大半的内存空间。

接着当然是找开发工程师去排查这个系统的代码问题了,为什么会创建那么多的对象,而且始终回收不掉?

这就是典型的内存泄漏!即系统创建了大量的对象占用了内存,其实很多对象是不需要使用的,而且还无法回收掉。

后来找到了一个原因,是在系统里做了一个JVM本地的缓存,把很多数据都加载到内存里去缓存起来,然后提供查询服务的时候直接从本地内存走。

但是因为没有限制本地缓存的大小,并且没有使用LRU之类的算法定期淘汰一些缓存里的数据,导致缓存在内存里的对象越来越多,进而造成了内存泄漏。

解决问题很简单,只要使用类似EHCache之类的缓存框架就可以了,他会固定最多缓存多少个对象,定期淘汰删除掉一些不怎么访问的缓存,以便于新的数据可以进入缓存中。


相关文章
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
1006 55
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
1015 6
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
742 29
JVM简介—1.Java内存区域
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
239 6
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
1458 8
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
7月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
524 5

热门文章

最新文章