java内存占用过大问题排查及解决
问题描述:
1.consumer实例堆内存分配4g,xmx设置为4g,在生产环境运行一段时间后,实际占用内存4.8g,业务运行正常,未出现OOM。
2、生产环境项目,均出现运行一段时间后,内存被占满但未OOM的情况。部分实例因内存占用过高导致被系统kill,一般需要通过增加机器、实例进行解决(资源浪费)。
造成的影响
3、服务器物理内存8g,部署了两个服务。consumer如过实际占用内存都超过4.8g,导致服务器物理内存不够用,出现告警而将占用内存最大进程kill掉,影响生产服务的可用性,后果十分严重。
4、如服务申请的内存超出了JVM能提供的内存大小(内存泄漏),将会导致java堆内存溢出,从而发生full gc,导致服务响应大幅度变慢,卡机等状态。
top 命令查看内存占用情况
jmap -heap 30788
jstat -gc 30788 查看各区域占用内存情况
jmap -dump:format=b,file=mall.dump 30788 生成dump文件
查看dump文件,看了一下文件占用,没有太大的文件占用
执行jstat -gc前先执行jinfo -flags 线程id查询jvm参数是否配置成功。
一、经调研,逐渐被淘汰的垃圾回收器比如ParallelOldGC和CMS,只要JVM申请过的内存,即使发生了GC回收了很多内存空间,JVM也不会把这些内存归还给操作系统。这就会导致top命令中看到的RSS(进程RAM中实际保存的总内存)只会越来越高,而且一般都会超过Xmx的值。JDK1.9以后。默认的垃圾回收器已经选择了G1。
G1相比CMS有更清晰的优势:
1)CMS采用"标记-清理"算法,所以它不能压缩,最终导致内存碎片化问题。而G1采用了复制算法,它通过把对象从若干个Region(独立区域)拷贝到新的Region(独立区域)过程中,执行了压缩处理,垃圾回收后会整合空间,无内存碎片。
2)在G1中,堆是由Region(独立区域)组成的,因此碎片化问题比CMS肯定要少的多。而且,当碎片化出现的时候,它只影响特定的Region(独立区域),而不是影响整个堆中的老年代。
3)而且CMS必须扫描整个堆来确认存活对象,所以,长时间停顿是非常常见的,无法预测停顿时间。而G1的停顿时间取决于收集的Region(独立区域)集合数量,在指定时间内只回收部分价值最大的空间,而不是整个堆的大小,所以相比起CMS,长时间停顿要少很多,可控很多。
4)G1选回收阶段不会产生“浮动垃圾”,由于只回收部分Region(独立区域),所以STW(stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起)时间我们可控,所以不需要与用户线程并发争抢CPU资源。而CMS并发清理需要占据一部分的CPU,会降低吞吐量。G1由于STW,所以不会产生"浮动垃圾",CMS在并发清理阶段会产生的无法回收的垃圾。
因此在以下场景下G1更适合:
1)服务端多核CPU、JVM内存占用较大的应用。
2)应用在运行过程中会产生大量内存碎片、需要经常压缩空间。
3)想要更可控、可预期的GC停顿周期;防止高并发下应用雪崩现象。
新参数(使用G1做为垃圾回收器)
-Xms4g -Xmx4g -Xss256k -XX:NewSize=512m -XX:MaxNewSize=512m -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=40 -XX:G1HeapRegionSize=8m -XX:+ExplicitGCInvokesConcurrent -XX:ParallelGCThreads=4 -Dsun.rmi.dgc.server.gcInterval=36000000 -Dsun.rmi.dgc.client.gcInterval=36000000 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize=256m -XX:MaxDirectMemorySize=512m -XX:GCTimeRatio=19 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/src/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
现用:
nohup java -Xmx8g -Xms8g -Xss256k -jar buyer-api-$version.jar> logs/buyer.out &
更改后:
nohup java -Xms8g -Xmx8g -Xss256k -XX:NewSize=512m -XX:MaxNewSize=512m -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=40 -XX:G1HeapRegionSize=8m -XX:+ExplicitGCInvokesConcurrent -XX:ParallelGCThreads=4 -Dsun.rmi.dgc.server.gcInterval=36000000 -Dsun.rmi.dgc.client.gcInterval=36000000 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize=256m -XX:MaxDirectMemorySize=1g -XX:GCTimeRatio=19 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/src/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar buyer-api-$version.jar> logs/buyer.out 2>&1 &
在使用 G1 垃圾回收器进行内存回收时,不同于传统的垃圾回收器,它使用了一种全局内存回收策略和区域化的垃圾回收方式。G1 将整个 Java 堆分成了不同的区域,每个区域的大小为 1MB 到 32 MB 不等。这使得 G1 比 CMS 垃圾回收器更为高效,可以更好地控制堆内存中的碎片化。
在 G1 中,如果某个区域中没有足够的垃圾需要回收,G1 可以将该区域标记为可用,并回收其中的内存。因此,与 CMS 垃圾回收器等传统的垃圾回收器不同的是,在 G1 中,即使没有足够的垃圾需要回收,JVM 也可能将未使用的内存(例如空闲的区域)返回给操作系统。这将导致在使用 G1 时,进程占用的内存可能要比最大堆大小(通过 -Xmx 参数设置)小很多。
因此,在使用 G1 垃圾回收器时,Java 进程实际占据的内存可能远小于最大堆大小(即 -Xmx 设置的值),即使看起来 Java 进程正在使用的内存比较少,实际它可能已经使用了大部分预分配的内存。