生产服务内存高问题

问题描述

  • 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。

感谢以下作者给与我的帮助

JAVA服务实例内存高问题排查及解决的更多相关文章

  1. java进程占用系统内存高,排查解决

    转自:http://blog.51cto.com/chengxiaobai/2052530?cid=695076 故障:最近收到生产服务器的报警短信以及邮件,报警内容为:内存使用率高于70%. 使用t ...

  2. Java服务,内存OOM问题如何快速定位? (转)

    转自:公众号  架构师之路 问题:有一个Java服务出现了OOM(Out Of Memory)问题,定位了好久不得其法,请问有什么好的思路么? OOM的问题,印象中之前写过,这里再总结一些相对通用的方 ...

  3. JAVA服务cpu占用高排查

    最近线上机器偶尔有台cpu达到100%,还居高不下.同样负载的其他机器却正常,我想肯定是代码哪里有问题了 首先我们top看下 可定位到对应占用高的PID 然后=>ps -mp PID -o TH ...

  4. java进程占用系统内存高,排查方法

    查看所有内存占用情况 top 定位线程问题(通过命令查看16764 进程的线程情况) ps p -L -o pcpu,pmem,pid,tid,time,tname,cmd 计数 ps p -L -o ...

  5. 一次java Cpu占用过高的排查

    某一个项目CPU占用率一直很高,经常在40%-50%之间,最近比较闲,就开始了排查工作. 1.通过 jstack命令输出进程的堆栈信息 jstack 2788 >C:\log.txt 将堆栈信息 ...

  6. 记一次JAVA进程导致Kubernetes节点CPU飙高的排查与解决

    一.发现问题 在一次系统上线后,我们发现某几个节点在长时间运行后会出现CPU持续飙升的问题,导致的结果就是Kubernetes集群的这个节点会把所在的Pod进行驱逐(调度):如果调度到同样问题的节点上 ...

  7. Java服务CPU占用高问题定位方法

    1. 概述 提供一种简单的方法来定位CPU高的问题. 找到CPU高的进程,比如232543: 执行top -H -p pid,找到占用CPU最高的线程号,比如232544,转换成16进制38c60: ...

  8. Java CPU占用过高问题排查,windows和Linux

    LINUX系统: linux系统比较简单: 1.使用命令 ps -ef | grep 找出异常java进程的pid.  找出pid为 20189 2. top -H -p 20189,所有该进程的线程 ...

  9. 简单查看tomcat中部署java服务的内存使用情况

    vim tomcat_mem.sh NAME=$1 #部署项目名称 #PID=`ps -ef|grep tomcat|grep $NAME|awk '{print $2}'` PID=`ps -ef| ...

随机推荐

  1. 数组 & 链表

    数组 是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据. 使用了连续的内存空间和相同类型的数据,使得它可以“随机访问”,但同时也让数组的删除,插入等操作变得非常低效, 为了保 ...

  2. 温故知新-java多线程&深入理解线程池

    文章目录 摘要 java中的线程 java中的线程池 线程池技术 线程池的实现原理 简述 ThreadPoolExecutor是如何运行的? 线程池运行的状态和线程数量 任务执行机制 队列缓存 Wor ...

  3. kernel list 实践

    list.h /******* 链表实现,来自内核 **************************************************/ /** * container_of - c ...

  4. 【深度思考】JDK8中日期类型该如何使用?

    在JDK8之前,处理日期时间,我们主要使用3个类,Date.SimpleDateFormat和Calendar. 这3个类在使用时都或多或少的存在一些问题,比如SimpleDateFormat不是线程 ...

  5. Jupyter notebook中的Cell and Line Magics

    参考资料: https://www.jianshu.com/p/81ada9234788 https://my.oschina.net/u/2306127/blog/832510 首先,Cell an ...

  6. Quartz.Net系列(七):Trigger之SimpleScheduleBuilder详解

    所有方法图 1.SimpleScheduleBuilder RepeatForever:指定触发器将无限期重复. WithRepeatCount:指定重复次数 var trigger = Trigge ...

  7. 韩顺刚-tcp报文头协议详细分析第一包数据:序号是0,发送数据的长度是0,因为没有收到对端的数据,所以确认号是0, Syn的标志位设置成1,这里没有发送的数据,只发送TCP的20个字节的头部

    TCP报文段首部格式 大部分TCP报文头部都是20个字节,有的数据包要加上选项. 上面一行代表4个字节,源端口和目的端口都是2个字节. TCP协议是面向字节流的协议 TCP是一段一段分块的发送数据的 ...

  8. 第七模块 :微服务监控告警Prometheus架构和实践

    119.监控模式分类~1.mp4 logging:日志监控,Logging 的特点是,它描述一些离散的(不连续的)事件. 例如:应用通过一个滚动的文件输出 Debug 或 Error 信息,并通过日志 ...

  9. JavaWeb网上图书商城完整项目--day02-9.提交注册表单功能之servlet层实现

    1.当用户在界面提交注册提交的时候,我们在UerServlet来实现具体的业务方法 标准demo: 1CommonUtils CommonUtils类就两个方法: lString uuid():生成长 ...

  10. React实战教程之从零开始手把手教你使用 React 最新特性Hooks API 打造一款计算机知识测验App

    项目演示地址 项目演示地址 项目代码结构 前言 React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但早期 React 类组件的写法略显繁琐.React Hoo ...