应用程序内存泄漏问题排查
1.文章的由来;
在日常运维过程中,会遇到服务器资源居高不下,或者CPU内存暴涨问题而引发的oom导致服务不可用 (大多数程序都是java应用),由此编写了该文章,为工作排查问题参考依据和快速定位问题方法;
2.基础知识储备;
(1).jvm 配置常见参数:
堆参数参数
参数 | 描述 |
-Xms | 设置 JVM启动时堆内存的初始化大小 |
-Xmx | 设置堆内存最大值 |
-Xmn | 设置年轻代的空间大小,剩下的为年老代的空间大小 |
-XX:PermGen | 设置永久代的内存初始化大小(JDK1.8 开始废弃永久代) |
-XX:MaxPermGen | 设置永久代的最大值 |
-XX:SurvivorRatio | 设置Eden区和Survivor区的空间比例:Eden/S0 =Eden/S1 默认8 |
-XX:NewRatio | 设置年老代和年轻代的比例大小,默认值是2 |
-XX:+UseSerialGC | 串行,young (年轻区)和Old(老年区)都使用串行,使用复制算法回收,逻辑简单高效,无现场切换开销 |
-XX:+UseParallelGC | 并行, young (年轻区)使用 Parallel scavenge 回收算法,会产生多个线程并行回收。通过-XX:ParallelGCThreads=n 参数指定有线程数,默认为cpu核数,Old(老年区):单线程 |
-XX:+UseParallelOldGC | 并行 和UseParallelGC 一样,young (年轻区) 和Old(老年区) 垃圾回收都使用多线程收集 |
-XX:+UseConMarkSweepGC | 并发,短暂停顿的并发收集,young区可以使用普通的或者parallel垃圾回收算法,由参数 --XX:+UseParNewGC 来控制; Old区 只使用Concurrent Mark Sweep |
-XX:+UseG1GC | 并行的,并发的和增量式短暂停顿的垃圾收集器。不区分young (年轻区) 和Old(老年区)空间。它把堆空间分为多个大小相等的区域。当进行垃圾收集时,他会优先收集存活对象较少的区域。因此叫"Garbage First” |
如上表所示,目前主要有串行、并行和并发三种,对于大内存的应用而言,串行的性能太低,因此使用到的主要是并行和并发两种。并行和并发 GC 的策略通过 UseParallelGC 和 UseConcMarkSweepGC 来指定,还有一些细节的配置参数用来配置策略的执行方式。例如:XX:ParallelGCThreads, XX:CMSInitiatingOccupancyFraction 等。 通常:Young 区对象回收只可选择并行(耗时间),Old 区选择并发(耗 CPU)。
生产环境服务配置参数
系统版本 | cpu | mem | jdk 版本 | jvm配置 | 备注 |
CentOS 7.5.1804 (Core) | 8C | 8G | java-1.8.0_121 | java -Xmx5g -Xms5g -Xmn3g -XX:MetaspaceSize=32m -XX:MaxMetaspaceSize=128m -XX:MaxDirectMemorySize=512m -XX:+UseCompressedClassPointers -XX:CompressedClassSpaceSize=64m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -XX:+CMSClassUnloadingEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=80 -verbose:gc -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+PrintCommandLineFlags -XX:+ExplicitGCInvokesConcurrent -Xloggc:/data/log/app-web/gc.log -Dapp.id=app-web -Dapollo.bootstrap.enabled=true -Duser.timezone=GMT+8 -Dapollo.bootstrap.namespaces=application -Deureka.instance.metadata-map.zone=zone-1 -Dservice.name=app-web -Denv=prod -Dprod_meta=http://configserver-prod.com -Druntime.env=prod -jar ./lib/app-web-1.8.10.0.jar | 微服务 springcloud 框架 |
常见组合
GC 调优原则
在调优之前,我们需要记住下面的原则:
多数的 Java 应用不需要在服务器上进行 GC 优化;
多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题;
在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合);
减少创建对象的数量;
减少使用全局变量和大对象;
GC 优化是到最后不得已才采用的手段;
在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多。
将转移到老年代的对象数量降低到最小;
减少 GC 的执行时间。
策略 1:将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
策略 2:大对象进入老年代,虽然大部分情况下,将对象分配在新生代是合理的。但是对于大对象这种做法却值得商榷,大对象如果首次在新生代分配可能会出现空间不足导致很多年龄不够的小对象被分配的老年代,破坏新生代的对象结构,可能会出现频繁的 full gc。因此,对于大对象,可以设置直接进入老年代(当然短命的大对象对于垃圾回收老说简直就是噩梦)。-XX:PretenureSizeThreshold 可以设置直接进入老年代的对象大小。
策略 3:合理设置进入老年代对象的年龄,-XX:MaxTenuringThreshold 设置对象进入老年代的年龄大小,减少老年代的内存占用,降低 full gc 发生的频率。
策略 4:设置稳定的堆大小,堆大小设置有两个参数:-Xms 初始化堆大小,-Xmx 最大堆大小。
策略5:注意: 如果满足下面的指标,则一般不需要进行 GC 优化:
MinorGC 执行时间不到50ms;
Minor GC 执行不频繁,约10秒一次;
Full GC 执行时间不到1s;
Full GC 执行频率不算频繁,不低于10分钟1次。
3.排查技巧战
1.内存泄漏排查技巧
系统命令
1.登陆探测服务器,首先是 top free df 三连;
jstat 是一个非常强大的 JVM 监控工具,一般用法是:jstat [-options] pid interval
它支持的查看项有:
-class 查看类加载信息
-compile 编译统计信息
-gc 垃圾回收信息
-gcXXX 各区域 GC 的详细信息 如 -gcold
3. jstat -gc pid [interval] 查看 java 进程的 GC 状态;
关注指标: FULL GC 。
4. jstack pid > jstack.log 查询线程栈 并保存现场;
栈的分析很简单,看一下线程数是不是过多,多数栈都在干嘛。
> grep 'java.lang.Thread.State' jstack.log | wc -l
> 464
过滤进程
grep -A 1 'java.lang.Thread.State' jstack.log | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n
10 at java.lang.Class.forName0(Native Method)
10 at java.lang.Object.wait(Native Method)
16 at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
44 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
344 at sun.misc.Unsafe.park(Native Method)
线程状态好像也无异常,接下来分析堆文件。
内存堆dump
5.使用 jmap -dump:format=b,file=heap.log pid 保存了堆现场,然后重启了应用服务
堆文件都是一些二进制数据,在命令行查看非常麻烦,Java 为我们提供的工具都是可视化的,Linux 服务器上又没法查看,那么首先要把文件下载到本地。
由于我们设置的堆内存为 4G,所以 dump 出来的堆文件也很大,下载它确实非常费事,不过我们可以先对它进行一次压缩。=
gzip 是个功能很强大的压缩命令,特别是我们可以设置 -1 ~ -9 来指定它的压缩级别,数据越大压缩比率越大,耗时也就越长,推荐使用 -6~7, -9 实在是太慢了,且收益不大,有这个压缩的时间,多出来的文件也下载好了。
MAT 是分析 Java 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类,果断选择 memory leak suspect。
windows 系统安装包如下:
https://www.eclipse.org/downloads/download.php?file=/mat/1.9.1/rcp/MemoryAnalyzer-1.9.1.20190826-win32.win32.x86_64.zip
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。