JAVA服务实例内存高问题排查及解决
生产服务内存高问题
问题描述
1、“计算中心” 服务在生产环境运行一段时间后,实际占用内存4.8G,业务运行正常,未出现OOM。(本文以此服务进行排查)
2、生产环境的老项目,均出现运行一段时间后,内存被占满但未OOM的情况。部分实例因内存占用过高导致被系统kill,一般需要通过增加机器、实例进行解决(资源浪费)。
造成的影响
1、服务器物理内存15g,部署了三个服务。如实际占用内存都超过4.8g,导致服务器物理内存不够用,出现告警而将占用内存最大进程kill掉,影响生产服务的可用性,后果十分严重。
2、如服务申请的内存超出了JVM能提供的内存大小(内存泄漏),将会导致java堆内存溢出,从而发生full gc,导致服务响应大幅度变慢,卡机等状态。
3、在公司大促等场景的情况下,内存占用很高的服务会带来很大风险,通常需提前联系运维同事对“计算中心”进行重启,增加了开发及运维同事维护的工作量。
排查过程
代码
(1)根据cat监控,获取“计算中心”中的热点方法,进行REVIEW,修正了部分可能会导致内存泄露的方法。并进行了观察。
(2)通过VisualVM监控,定位到部分耗时较久的操作DB热点方法,通过增加索引等方式,把查询性能控制在毫秒级。
(3)dump“计算中心”的内存镜像,通过MAT等工具观察各个对象在堆空间中所占用的内存大小、类实例数量、对象引用关系。
结论:通过以上三点,未解决和定位“计算中心”内存高问题。由此可以认为,“计算中心”的内存问题与代码无关。
系统
通过java ps| aux java 查看,“计算中心”服务实际占用的内存在4.9G左右,超过了JVM堆内存设置的大小但并未出现OOM,业务正常运行。通过free -g命令,可以发现buff/cache,3个g左右。centos中内存的分配是buff/cache + free + used=物理内存大小,系统分配给临时文件系统的大小默认是用掉一半的物理内存,这样会造成buff/cache很大,而free很小。最终结论可能为服务内存没有释放使用了buff/cache。导致服务内存占用很高。
结论:和SRE沟通实际重启服务后,内存使用率立刻降低,但是buff/cache的大小没有变化。由此可以认为,“计算中心”的内存问题与系统缓存无关。
JVM
1、通过VisualVM监控生产环境computing内存使用情况得出,在服务内存占用4.8g的情况下,堆内存(新生代 + 老年代)正常GC,在增长到2g左右会GC到300~500m,Matespace仅使用了120m左右。检查了生产JVM参数。项目启动参数没有配置:-XX:MaxDirectMemorySize,来指定最大的堆外内存大小。这个阈值不配置的话,默认占用-Xmx相同的内存。在堆内内存正常的情况下,怀疑是堆外内存占用了大部分内存,导致服务内存占用很高。
结论:和SRE沟通,在生产环境找了两台计算中心服务实例配置 -XX:MaxDirectMemorySize 参数后实际观察后,仍未解决“计算中心”内存高问题。由此可以认为,“计算中心”的内存问题与堆外内存无关。
2、在发现-XX:MaxDirectMemorySize 指定堆外内存大小的参数没有配置后。我检查了“计算中心”服务的启动参数并且和之前生产环境的服务进行了对比,发现了以下问题。
1)生产及测试环境JVM参数配置混乱,同一应用不同实例多套启动参数配置。
2)服务启动参数,未区分JDK版本。如:JDK1.7、JDK1.8参数混用。
3)生产服务根据模板部署,导致必要JVM参数未配置,部分参数配置不需要、不合理。
4)不同类型的应用,采用统一的启动参数配置,不具有针对性。
在以上问题的基础上,基于目前生产环境各项目统一使用的"CMS垃圾回收器"进行参数调整。针对计算中心的JDK版本(1.8),出了一套JVM配置方案。并在生产服务器调整后重启观察。
结论:“计算中心”生产环境服务,在运行一段时间后,仍出现内存占用高问题。由此可以认为,“计算中心”的内存问题与不同JDK版本的参数混用问题无关。
3、经调研,逐渐被淘汰的垃圾回收器比如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,并增加G1相关的优化内存的参数,在生产服务器进行观察一周后发现服务内存始终稳定在了3.3G左右,业务处理性能稳定,成功解决了“计算中心”服务占用内存较高的问题,提升了系统的可用性,无需通过增加物理资源来提升服务整体性能。
CMS升级为G1方式
计算中心“生产全部服务实例部署的服务器,使用的是JDK1.8,JDK1.8支持G1垃圾回收器,故将服务启动参数进行统一调整:
1)原参数(主要问题:使用CMS版本,JDK1.7,1.8参数混用,未指定堆外内存大小)
/opt/java/jdk1.8.0_102/bin/java -Dapp.home=${APP_HOME} -Dspring.profiles.active=prd -Dserver.port=${SERVER_PORT} -server -Xms4G -Xmx4G -Xmn2g -Xss256k -XX:PermSize=128m -XX:MaxPermSize=512m -Djava.awt.headless=true -Dfile.encoding=utf-8 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:AutoBoxCacheMax=20000 -XX:-OmitStackTraceInFastThrow -XX:ErrorFile=${APP_HOME}/logs/hs_err_%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${APP_HOME}/logs/ -Xloggc:${APP_HOME}/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar ${APP_HOME}/webapps/${JAR_NAME} ${SERVER_PORT}"
2)新参数(使用G1做为垃圾回收器)
/opt/java/jdk1.8.0_102/bin/java -Dapp.home=${APP_HOME} -Dspring.profiles.active=prd -Dserver.port=${SERVER_PORT} -server -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:ErrorFile=${APP_HOME}/logs/hs_err_%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${APP_HOME}/logs/ -Xloggc:${APP_HOME}/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar ${APP_HOME}/webapps/${JAR_NAME} ${SERVER_PORT}"
注:不同服务器环境,不同容器,脚本配置各不相同,要在对应脚本的基础上进行针对性升级。
生产G1回收器主要参数说明
JVM相关概念说明
JDK1.7内存模型
实际占用内存大小(参数):-XX:MaxPermSize(非堆) + -Xmx(堆) + -Xss(栈) + -XX:MaxDirectMemorySize(堆外)
JDK1.8内存模型
实际占用内存大小(参数):-XX:MaxMateSpaceSize(堆外) + -Xmx(堆) + -Xss(栈) + -XX:MaxDirectMemorySize(堆外)
GC流程图
1、什么时候触发Minor GC
2、触发Minor GC 的过程
3、Full GC 的过程
1、新创建的对象一般会被分配在新生代中。常用的新生代的垃圾回收器是 ParNew 垃圾回收器,它按照 8:1:1 将新生代分成 Eden 区,以及两个 Survivor 区。创建的对象将 Eden 区全部挤满,这个对象就是「挤满新生代的最后一个对象」。此时,Minor GC 就触发了。
2、在正式 Minor GC 前,JVM 会先检查新生代中对象,是比老年代中剩余空间大还是小。Minor GC 之后 Survivor 区放不下剩余对象,这些对象就要进入到老年代,所以要提前检查老年代是不是够用。
3、老年代剩余空间大于新生代中的对象大小,那就直接 Minor GC,GC 完 survivor 不够放,老年代也绝对够放。老年代剩余空间小于新生代中的对象大小,这时候就要进入老年代空间分配担保规则。
4、老年代空间分配担保规则:如果老年代中剩余空间大小,大于历次 Minor GC 之后剩余对象的大小,那就允许进行 Minor GC。因为从概率上来说,以前的放的下,这次的也应该放的下。那就有两种情况:
老年代中剩余空间大小,大于历次 Minor GC 之后剩余对象的大小,进行 Minor GC
老年代中剩余空间大小,小于历次 Minor GC 之后剩余对象的大小,进行 Full GC,把老年代空出来再检查。
5、结合第四步,开启老年代空间分配担保规则只能说是大概率上来说,Minor GC 剩余后的对象够放到老年代,如果放不下:Minor GC 后会有这样三种情况:Minor GC 之后的对象足够放到 Survivor 区,GC 结束。
Minor GC 之后的对象不够放到 Survivor 区,接着进入到老年代,老年代能放下,那也可以,GC 结束。
Minor GC 之后的对象不够放到 Survivor 区,老年代也放不下,那就只能 Full GC。
6、以上是成功 GC 的例子,以下3 中情况,会导致 GC 失败,报 OOM:
紧接上一节 Full GC 之后,老年代任然放不下剩余对象,就只能 OOM。
未开启老年代分配担保机制,且一次 Full GC 后,老年代任然放不下剩余对象,也只能 OOM。
开启老年代分配担保机制,但是担保不通过,一次 Full GC 后,老年代任然放不下剩余对象,也是能 OOM。
注:
- 老年代分配担保机制在JDK1.5以及之前版本中默认是关闭的,需要通过HandlePromotionFailure手动指定,JDK1.6之后就默认开启。如果我们生产环境服务使用的是JDK/1.7JDK1.8,所以不用再手动去开启担保机制。
- Full GC主要指新生代、老年代、metaspace上的全部GC。
感谢以下作者给与我的帮助
- 图解GC流程:https://www.cnblogs.com/shuiyj/p/12640692.html
- CMS垃圾回收升级G1回收器实践:http://arick.net/content/44
- JAVA常见问题分析:https://blog.51cto.com/hmtk520/2067043
JAVA服务实例内存高问题排查及解决的更多相关文章
- java进程占用系统内存高,排查解决
转自:http://blog.51cto.com/chengxiaobai/2052530?cid=695076 故障:最近收到生产服务器的报警短信以及邮件,报警内容为:内存使用率高于70%. 使用t ...
- Java服务,内存OOM问题如何快速定位? (转)
转自:公众号 架构师之路 问题:有一个Java服务出现了OOM(Out Of Memory)问题,定位了好久不得其法,请问有什么好的思路么? OOM的问题,印象中之前写过,这里再总结一些相对通用的方 ...
- JAVA服务cpu占用高排查
最近线上机器偶尔有台cpu达到100%,还居高不下.同样负载的其他机器却正常,我想肯定是代码哪里有问题了 首先我们top看下 可定位到对应占用高的PID 然后=>ps -mp PID -o TH ...
- java进程占用系统内存高,排查方法
查看所有内存占用情况 top 定位线程问题(通过命令查看16764 进程的线程情况) ps p -L -o pcpu,pmem,pid,tid,time,tname,cmd 计数 ps p -L -o ...
- 一次java Cpu占用过高的排查
某一个项目CPU占用率一直很高,经常在40%-50%之间,最近比较闲,就开始了排查工作. 1.通过 jstack命令输出进程的堆栈信息 jstack 2788 >C:\log.txt 将堆栈信息 ...
- 记一次JAVA进程导致Kubernetes节点CPU飙高的排查与解决
一.发现问题 在一次系统上线后,我们发现某几个节点在长时间运行后会出现CPU持续飙升的问题,导致的结果就是Kubernetes集群的这个节点会把所在的Pod进行驱逐(调度):如果调度到同样问题的节点上 ...
- Java服务CPU占用高问题定位方法
1. 概述 提供一种简单的方法来定位CPU高的问题. 找到CPU高的进程,比如232543: 执行top -H -p pid,找到占用CPU最高的线程号,比如232544,转换成16进制38c60: ...
- Java CPU占用过高问题排查,windows和Linux
LINUX系统: linux系统比较简单: 1.使用命令 ps -ef | grep 找出异常java进程的pid. 找出pid为 20189 2. top -H -p 20189,所有该进程的线程 ...
- 简单查看tomcat中部署java服务的内存使用情况
vim tomcat_mem.sh NAME=$1 #部署项目名称 #PID=`ps -ef|grep tomcat|grep $NAME|awk '{print $2}'` PID=`ps -ef| ...
随机推荐
- AsyncTask源码解读
AsyncTask源码解读 一.基本使用 protected void onPreExecute() protected abstract Result doInBackground(Params.. ...
- Uber基于Apache Hudi构建PB级数据湖实践
1. 引言 从确保准确预计到达时间到预测最佳交通路线,在Uber平台上提供安全.无缝的运输和交付体验需要可靠.高性能的大规模数据存储和分析.2016年,Uber开发了增量处理框架Apache Hudi ...
- 有关指针 -> 和* 的重载
1, #include<iostream> #include<string> using namespace std; class test{ int i; public: t ...
- Linux系统如何设置开机自动运行脚本?
大家好,我是良许. 在工作中,我们经常有个需求,那就是在系统启动之后,自动启动某个脚本或服务.在 Windows 下,我们有很多方法可以设置开机启动,但在 Linux 系统下我们需要如何操作呢? Li ...
- 如何在微信小程序中使用骨架屏
先上效果图
- Mac Book 问题汇集
1.mac wifi 无法连接问题 1. 由于插入的USB 转接头导致,USB转接口带有网线插口,机器默认网页接口接口导致. 解决方案: 拔掉转接口,连上WiFi ,再插入转接口使用 2.可以是路由器 ...
- 在CentOS下利用Docker一键安装seafile
https://cloud.seafile.com/published/seafile-manual-cn/docker/pro-edition/%E7%94%A8Docker%E9%83%A8%E7 ...
- Express4.x之中间件与路由详解及源码分析
Application.use() Application.router() express核心源码模拟 一.express.use() 1.1app.use([path,] callback [, ...
- Anaconda 安装 以及conda使用
下载 https://www.anaconda.com/distribution/#macos 管理 conda 版本查看 conda --version conda 版本更新 conda updat ...
- 『图论』LCA 最近公共祖先
概述篇 LCA (Least Common Ancestors) ,即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先. LCA 可分为在线算法与离线算法 ...