tomcat与oom_killer

来源:互联网 发布:广州积分入户积分算法 编辑:程序博客网 时间:2024/06/05 20:32

1、场景
聚石塔内的服生产务器,每隔2、3个星期都会自动挂掉,导致业务生产受阻。因为之前也发生过这种情况,以前在服务器挂掉后马上去查看进程,想及时打印出堆栈信息,或者查看GC日志,(可以查看我以前的日志:http://blog.csdn.net/zhangyufeijiangxi/article/details/74951431)。但是发现进程已经不存在了,这就没法分析,我们认为是OOM导致系统直接挂掉了进程,所以增加了配置:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/tomcat/logs
我们期待在下一次出现问题时能及时打印堆栈信息再挂掉

2、重现
早上一起来发生警报,说服务器挂掉了,马上登陆服务器,查找进程,进程还是被挂掉而不是被卡主。
查看打印的堆栈信息,很遗憾,还是没有打印出堆栈信息,说明进程挂掉不是因为JVM的OOM导致的,此时我想到去查看Linux系统的日志,因为以前学Linux记得Linux可以记录进程信息的,如果出错了多多少少会有信息打印

看时间,事故发生时间:8:50左右,直接查看系统日志:
less /var/log/message
发现如下信息:
Jul 30 04:02:01 jstuvdvcv9kr8z syslogd 1.4.1: restart.
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: tail invoked oom-killer: gfp_mask=0x201d2, order=0, oomkilladj=0
Aug 1 08:51:45 jstuvdvcv9kr8z kernel:
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: Call Trace:
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] out_of_memory+0x8e/0x2f3
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] __alloc_pages+0x27f/0x308
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] __do_page_cache_readahead+0x96/0x17b
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] filemap_nopage+0x14c/0x360
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] __handle_mm_fault+0x1fd/0x103b
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] thread_return+0x62/0xfe
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] do_page_fault+0x499/0x842
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] hrtimer_cancel+0xc/0x16
Aug 1 08:51:45 jstuvdvcv9kr8z kernel: [] do_nanosleep+0x47/0x70
Aug 1 08:51:47 jstuvdvcv9kr8z kernel: [] hrtimer_nanosleep+0x58/0x118
Aug 1 08:51:51 jstuvdvcv9kr8z kernel: [] error_exit+0x0/0x84
。。。。。。
Aug 1 08:51:52 jstuvdvcv9kr8z kernel: Out of memory: Killed process 2395, UID 0, (java).
看到第一句和最后一句:
oom-killer被唤醒,然后kill了pid2395,该进程是一个java进程,我们的服务器一个是生产进程,一个是测试进程,测试进程还存在,所以可以断定被kill的是生产进程。
oom_killer:会选择占用内存最高的,然后和oom_adj权限进行加权比较,一般是选择占用内存最大的进行进行关闭

3、oom_killer介绍
起作用条件:Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。
原理:优化,申请内存不一定马上使用,可能申请后,如果你在使用的时候有资源释放了,那么就说明 你的申请是有效的
容易出现的问题:
如果使用的时候,还是没有释放出足够的内存,
当你用到这个Overcommit给你的内存的时候,系统还没有资源的话,OOM killer就跳出来了。

oom_killer:策略
Linux下有3种Overcommit的策略(参考内核文档:vm/overcommit-accounting),可以在/proc/sys/vm/overcommit_memory配置(取0,1和2三个值,默认是0)。

(1)0:启发式策略,比较严重的Overcommit将不能得逞,比如你突然申请了128TB的内存。而轻微的overcommit将被允许。另外,root能Overcommit的值比普通用户要稍微多些。

(2)1:永远允许overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。

(3)2:永远禁止overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序

关闭进程选择策略:没用的且耗内存多的程序被枪
具体解释:
Linux下这个选择策略也一直在不断的演化。作为用户,我们可以通过设置一些值来影响OOM killer做出决策。Linux下每个进程都有个OOM权重,在/proc/pid/oom_adj里面,取值是-17到+15,取值越高,越容易被干掉。

最终OOM killer是通过/proc/pid/oom_score这个值来决定哪个进程被干掉的。这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低。总之,总的策略是:损失最少的工作,释放最大的内存同时不伤及无辜的用了很大内存的进程,并且杀掉的进程数尽量少
具体参考:http://blog.csdn.net/fjssharpsword/article/details/9341563

4、问题分析:
看日志是因为jvm申请了超过内存的空间,导致被进程被进程干掉了,那么为什jvm没有先发生OOM而打印出堆栈信息呢,猜测是:配置的内存大于实际的内存,jvm分配时,与-xms进行了匹配,发现可以申请分配,但是-xms大于实际内存,导致,系统监控oom-killer先启动,把进程干掉,但是jvm还没发现申请不成功,即被杀死了,所以打印不出来堆栈信息

5、解决方案
a、加内存:cat /proc/meminfo
查看内存使用情况:
MemTotal: 4054120 kB
MemFree: 571284 kB
Buffers: 22564 kB
Cached: 506968 kB
SwapCached: 0 kB
Active: 3024304 kB
总共4G的内存,只剩下500M左右,而且监控top,一直处于高内存状态,达到60%,说明系统内存使用率过高,CPU也是,过高,所以申请对服务器升级,多花点钱效果是最好的

b、挤牙膏似得方案:修改堆栈配置
top命令监控到的情况:
生产进程:内存占用2.9G
测试进程:内存占用1.2G
生产配置:-Xms2048m -Xmx2048m -XX:PermSize=64m -XX:MaxPermSize=256m
测试配置:-Xms256m -Xmx512m -XX:PermSize=64m -XX:MaxPermSize=256m
生产配置理论最大值=2048+256+native+寄存器=2.25G+其他
测试配置理论最大值=512+256+native+寄存器=0.75G+其他
生产+测试可达到3G,也就是说还只剩下1G内存,需要维持Linux内核进程及其他进程,还有java进程的其他如直接内存,寄存器等内存消耗,
如果某个时间,某个变量较大,那么就会产生oom_killer,导致进程关掉
方案:把堆减小,降为1.5G,y因为堆降低了,那么GC次数将增多,牺牲性能换业务正常流转

c、更改策略:如果发生oom_killer,先选测试进程,不选生产进程
/proc/pid/oom_adj 修改生产进程的优先级,-17~15.默认0,越小越不容易选中

d、分析代码:
为什么会出现某个时间点,生产进程(也可能不是它)突然要分配大量的内存,如果能找到该段代码,可以进行重新设计,如果是请求过多,能不能用阻塞队列缓存起来先不处理等等方案
后续如果我找到了对应的代码,我将继续写出来,因为目前没有堆栈信息,不好找