linux下tomcat占用内存过多问题处理

一次服务器内存调优的记录

前言

我们部署在linux上的服务,不约而同的出现了内存占用过高的情况,有些甚至都快到了100%了, 照理说服务应该会出现无法正常运行的情况,但是并没有,很奇怪,最明显的现象是两个tomcat占用的内存不断的在增加,下意识的想到了内存泄漏,然后展开了如下的一系列定位。

jstat中的各个指标含义

S0C:年轻代中第一个survivor(幸存区)的容量 (kb)
S1C:年轻代中第二个survivor(幸存区)的容量 (kb)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (kb)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (kb)
EC:年轻代中Eden(伊甸园)的容量 (kb)
EU:年轻代中Eden(伊甸园)目前已使用空间 (kb)
OC:Old代的容量 (kb)
OU:Old代目前已使用空间 (kb)
PC:Perm(持久代)的容量 (kb)
PU:Perm(持久代)目前已使用空间 (kb)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
CGC:并发 GC Stop-The-World 的次数
CGTT:并发 GC Stop-The-World 的时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
NGCMN:年轻代(young)中初始化(最小)的大小 (kb)
NGCMX:年轻代(young)的最大容量 (kb)
NGC:年轻代(young)中当前的容量 (kb)
OGCMN:old代中初始化(最小)的大小 (kb)
OGCMX:old代的最大容量 (kb)
OGC:old代当前新生成的容量 (kb)
PGCMN:perm代中初始化(最小)的大小 (kb)
PGCMX:perm代的最大容量 (kb)
PGC:perm代当前新生成的容量 (kb)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
P:perm代已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (kb)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (kb)
ECMX:年轻代中Eden(伊甸园)的最大容量 (kb)
DSS:当前需要survivor(幸存区)的容量 (kb)(Eden区已满)
CCS: 类指针压缩空间使用率,可以用-XX:-UseCompressedClassPointers关闭
TT: 持有次数限制
MTT : 最大持有次数限制

一、服务器情况

服务持续运行一个月左右
两个内存占用异常的tomcat


image.png
用户名 进程号 配置
root 64317 -Xms700m -Xmx700m -server -XX:+HeapDumpOnOutOfMemoryError -XX:MaxDirectMemorySize=64M -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath
root 214311 -Xms1024m -Xmx3072m -XX:CICompilerCount=2 -XX:MaxDirectMemorySize=64M -XX:InitiatingHeapOccupancyPercent=70 -XX:ParallelGCThreads=2 -XX:ConcGCThreads=2 -XX:G1ReservePercent=10

1.1 GC容量情况

image.png

1.2 GC情况

image.png

1.3 详细分析

(1)两个tomcat的gc情况可以发现进程号为64317的进程YGC次数明显超过进程号为214311的进程,它的新生代回收时间(YGCT)较长也可以理解,我们假设它的吞吐量较高,所以新生代频繁生成,频繁回收。
(2)再来说说FGC,full gc一般发生在堆内存剩余不足的情况下,如果服务器调参合理的话,一般很少会发生full gc。然后我们比较发现64317虽然YGC较多,但是FGC明显少于214311,可见214311的堆内存压力明显高于64317,同时暴露出一个问题,214311的堆内存分配存在不合理,导致了频繁full gc。
(3)再来看看CGC,64317的CGC非常恐怖,居然有5000多次,也导致了CGCT时间过长,虽然在使用过程中产生STW不可避免,但是Stop-The-World对系统性能存在影响,我们应该尽量去减少它的产生。
在新生代进行的GC叫做minor GC,在老年代进行的GC都叫major GC,Full GC同时作用于新生代和老年代。在垃圾回收过程中经常涉及到对对象的挪动(比如上文提到的对象在Survivor 0和Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”,导致系统全局停顿。
综上所述,可以将214311的堆内存Xmx调大,减少其FGC,而针对64317,可以调整其年轻代的堆内存分配,减少它的GC停顿时间。GC停顿时间长会导致服务停止,没有响应,如遇到HA系统,可能引起主备切换,严重危害生产环境。

二、JVM优化

2.1 参数调整

因为214311这个进程正在使用,暂时不能停止,所以先对64317进程进行调优,对JVM进行调参优化,减少其CGC次数,原先的配置中我们发现没有配置年轻代大小,这里我们设置一下年轻代大小(有必要的话可以设置一下年轻代初始值大小),如下所示

用户名 进程号 配置
root 390665 -Xms768m -Xmx768m -Xmn384m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=10.19.164.40 -Dcom.sun.management.jmxremote.port=19999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -server -XX:+HeapDumpOnOutOfMemoryError -XX:MaxDirectMemorySize=64M

其中MetaspaceSize是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加上线也可能降低。MaxMetaspaceSize用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存。

2.2 结果

最终修改了tomcat的配置之后,运行了两天,这里的FGC,次数不是很准确,本身没这么多,我手动触发了几次


image.png

三、再次观察RES内存

其实,到现在tomcat的内存占用异常的情况还是未有好转,这个时候就换个角度想想,是不是服务器的问题导致的呢,对比windows上相同的服务,占用内存就正常很多,然后我发现看到的RES是包含了共享库和共享内存。

3.1打破砂锅弄明白

既然要查看他的内存情况,那么就通过pmap把所有内存信息打印出来,如通过pmap pid -->test.txt将java进程的内存映射打印出来(也可以通过pldd pid -->test.txt将共享对象打印出来)。数据打印出来之后我很中二的先用代码比较器比较几个tomcat的共享库信息,然后计算了公用的库占用内存大小,这种特别傻的行为,各位童鞋就不用再做了T_T!。
然后我发现pmap文档中有一些奇奇怪怪的64MB的内存块

image.png

这是啥,百度了好一会之后终于有了结果,arena内存池,是linux用于管理内存的一种机制。如果不配置的情况下,它可以给每个进程提供cores864MB的空间,我们的服务器上有16个核,算起来最多能占用8个G,现在还不算它的巅峰,然后把这些64MB去掉的话我们的内存占用情况其实很正常。

3.2 修改配置

(1)百度上查的
/etc/profile追加一行:
export MALLOC_ARENA_MAX=1
source /etc/profile
改了之后发现没鬼用
我们通过strings /proc/{pid}/environ 查看进程对应的环境变量时,发现没有这个环境变量
(2)查看catalina.sh脚本
脚本中有这么一段逻辑

if [ -r "$CATALINA_BASE/bin/setenv.sh" ]; then
  . "$CATALINA_BASE/bin/setenv.sh"
elif [ -r "$CATALINA_HOME/bin/setenv.sh" ]; then
  . "$CATALINA_HOME/bin/setenv.sh"
fi

但是在bin文件夹下没有这个setenv.sh,所以我们需要自己touch一个setenv.sh,然后在里面添加如下内容

export MALLOC_ARENA_MAX=4
#这个是为了证明是不是真的有执行这个脚本
echo 'wcf set arena'

3.3 最终结果

然后保存退出,重启tomcat,查看res和virt都有明显的锐减,说明我们的配置生效了,然后再查看进程对应的环境变量,确认一下,确实多了一个配置。


image.png

然后我们再top一下


image.png

上面一个是没改过的,下面的是我改过的,两者的virt差距相当明显。

3.4说明

原因其实java进程开启多线程调用数据,会采用内存池,保证每个线程都有一个堆,这样避免内存分配时需要额外的锁来降低性能。但是过多的内存池使用会导致有res暴涨,还可能会出现内存空洞,因为64MB的内存块需要栈顶释放了之后才会被系统回收,否则还是计入当前进程内的。如果栈中其他的都释放了,但是栈顶的还在使用中,那这个内存块就有点浪费了,就是所谓的内存空洞。所以我们可以限制内存池的个数,避免这种现象,具体设置MALLOC_ARENA_MAX为多少需要根据业务需求实测,我配置的4只是根据网上的经验尝试了一下

四、其他

image.png

我们通过RES之和得到内存已经使用了85%左右,但是根据top指令中展示的信息used/total=73%不到点,其实我们实际可用的内存是avail,因为在linux中它尽可能的cache和buffer一些数据,提高文件读取的性能,以方便下次使用。但实际上这些内存,如果需要的话,也是可以立刻拿来使用的,当然也不是所有的buff/cache都是可以拿来使用的。
执行下面的这个命令,free立马就变多了
echo 3 > /proc/sys/vm/drop_caches


image.png

五、采用tcmalloc

PS:最好在一个能够让你任性的环境里瞎搞

5.1 安装libunwind

(1)首先我们下载libunwind
libunwind下载地址
(2)解压make

tar zxvf libunwind-1.2.tar.gz   
cd libunwind-1.2/
./configure 
make    
make install    
make clean

5.2 安装gperftools

(1)下载
gperftools
(2)解压make

tar zxvf google-perftools-2.7.tar.gz
cd gperftools-2.7/  
./configure 
make    
make install    
make clean  
#加入/usr/local/lib(libunwind的lib所在目录)    
echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf    
#使libunwind生效   
ldconfig

5.3 确认是否生效

通过ldconfig -v查看


image.png

5.4 给进程配置环境变量

在tomcat的bin文件夹下面的setenv.sh中写入如下内容

ldconfig    
export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
#你想要把tcmalloc的heap文件写入到哪个文件夹
export HEAPPROFILE=/test/wcf/tcmalloc/heap

5.5 重启tomcat进程

image.png

这里可以看到62078这个进程(和上面优化的5194是同一个服务)virt比上面arena配置成4个时候更低(不过这不重要),然后RES也正常,剩下的就是性能测试和稳定性测试了。

六、最后的最后

(1)修改完arena的时候,前几天看它的时候每天有几百KB的内存上涨,到今天它又回落了,所以说是一个波动的形式
(2)修改完tcmalloc之后,当天就会在一定范围内产生波动,跟网上夸它的描述比较相符,看起来是它对内存的分配和回收上的表现更为优秀
到这里排查tomcat内存异常算是结束了,tcmalloc和修改arena的配置都是行的通的,相对来说tcmalloc更优秀一点(再未充分测试性能的前提下)。然后我采访了几个小伙伴,其中有人所在公司采用的就是tcmalloc,所以在生产环境上用也是行的通的。
参考文献:
Java GC 变量含义(S0 S1 E O P YGC YGCT FGC FGCT GCT)
当Java虚拟机遇上Linux Arena内存池

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容