tomcat 与 oom_killer

场景

    在一台centos上,我启动了tomcat,运行之后内存剩余500M,但是第二天tomcat已经被停止了。要知道申请JVM内存是成倍申请,申请到了之后不会回收,也就是说,我的tomcat启动成功后,就拥有固定的内存,而我可以保证它的使用不会超出该内存而去申请下一段内存,但是为什么莫名的停止呢?而且没有留下任何日志,说明是系统kill掉的。

分析

    那么我们想办法打出日志,加上以下参数,我们期待在下一次出现问题时能及时打印堆栈信息再挂掉。

1
2
# 修改$TOMCAT_HOME/bin/catalina.sh
JAVA_OPTS="-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/usr/local/tomcat/logs"

    第二天起床照样发现已被kill,查看日志,/var/log/message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Jul  3 03:53:03 rcmsit kernel: YDService invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Jul 3 03:53:04 rcmsit kernel: YDService cpuset=/ mems_allowed=0
Jul 3 03:53:04 rcmsit kernel: CPU: 1 PID: 9434 Comm: YDService Not tainted 3.10.0-514.26.2.el7.x86_64 #1
Jul 3 03:53:04 rcmsit kernel: Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011
Jul 3 03:53:04 rcmsit kernel: ffff880119648000 00000000210107be ffff88005c037938 ffffffff81687133
Jul 3 03:53:04 rcmsit kernel: ffff88005c0379c8 ffffffff816820de ffffffff810eb0dc ffff88005c14b980
Jul 3 03:53:04 rcmsit kernel: ffff88005c14b998 0000000000000202 ffff880119648000 ffff88005c0379b8
Jul 3 03:53:04 rcmsit kernel: Call Trace:
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81687133>] dump_stack+0x19/0x1b
Jul 3 03:53:04 rcmsit kernel: [<ffffffff816820de>] dump_header+0x8e/0x225
Jul 3 03:53:04 rcmsit kernel: [<ffffffff810eb0dc>] ? ktime_get_ts64+0x4c/0xf0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff8113d22f>] ? delayacct_end+0x8f/0xb0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81184d0e>] oom_kill_process+0x24e/0x3c0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff811847ad>] ? oom_unkillable_task+0xcd/0x120
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81184856>] ? find_lock_task_mm+0x56/0xc0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81093c0e>] ? has_capability_noaudit+0x1e/0x30
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81185546>] out_of_memory+0x4b6/0x4f0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81682be7>] __alloc_pages_slowpath+0x5d7/0x725
Jul 3 03:53:04 rcmsit kernel: [<ffffffff8118b655>] __alloc_pages_nodemask+0x405/0x420
Jul 3 03:53:04 rcmsit kernel: [<ffffffff811cf9ca>] alloc_pages_current+0xaa/0x170
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81180be7>] __page_cache_alloc+0x97/0xb0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81183760>] filemap_fault+0x170/0x410
Jul 3 03:53:04 rcmsit kernel: [<ffffffffa01b7016>] ext4_filemap_fault+0x36/0x50 [ext4]
Jul 3 03:53:04 rcmsit kernel: [<ffffffff811ac83c>] __do_fault+0x4c/0xc0
Jul 3 03:53:04 rcmsit kernel: [<ffffffff811accd3>] do_read_fault.isra.42+0x43/0x130
Jul 3 03:53:04 rcmsit kernel: [<ffffffff811b1461>] handle_mm_fault+0x6b1/0x1000
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81692cc4>] __do_page_fault+0x154/0x450
Jul 3 03:53:04 rcmsit kernel: [<ffffffff81692ff5>] do_page_fault+0x35/0x90
Jul 3 03:53:04 rcmsit kernel: [<ffffffff8168f208>] page_fault+0x28/0x30
Jul 3 03:53:04 rcmsit kernel: Mem-Info:
Jul 3 03:53:04 rcmsit kernel: active_anon:625329 inactive_anon:205647 isolated_anon:0#012 active_file:2142 inactive_file:3816 isolated_file:0#012 unevictable:0 dirty:0 writeback:0 unstable:0#012 slab_reclaimable:26535 slab_unreclaimable:52499#012 mapped:615 shmem:12902
0 pagetables:3722 bounce:0#012 free:33074 free_pcp:192 free_cma:0
Jul 3 03:53:04 rcmsit kernel: Node 0 DMA free:15400kB min:276kB low:344kB high:412kB active_anon:108kB inactive_anon:192kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writ
eback:0kB mapped:0kB shmem:44kB slab_reclaimable:68kB slab_unreclaimable:36kB kernel_stack:0kB pagetables:20kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
Jul 3 03:53:04 rcmsit kernel: lowmem_reserve[]: 0 3327 3773 3773
Jul 3 03:53:04 rcmsit kernel: Node 0 DMA32 free:108368kB min:59340kB low:74172kB high:89008kB active_anon:2321152kB inactive_anon:634816kB active_file:7216kB inactive_file:13476kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:3653624kB managed:3408868kB
mlocked:0kB dirty:0kB writeback:0kB mapped:2328kB shmem:500612kB slab_reclaimable:91688kB slab_unreclaimable:184240kB kernel_stack:12304kB pagetables:12780kB unstable:0kB bounce:0kB free_pcp:696kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unrecla
imable? no
Jul 3 03:53:04 rcmsit kernel: lowmem_reserve[]: 0 0 446 446
Jul 3 03:53:04 rcmsit kernel: Node 0 Normal free:9892kB min:7964kB low:9952kB high:11944kB active_anon:180056kB inactive_anon:187580kB active_file:1904kB inactive_file:4676kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:524288kB managed:457256kB mlocke
d:0kB dirty:0kB writeback:0kB mapped:132kB shmem:15424kB slab_reclaimable:14384kB slab_unreclaimable:25720kB kernel_stack:2208kB pagetables:2088kB unstable:0kB bounce:0kB free_pcp:296kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:96 all_unreclaimable? no
Jul 3 03:53:04 rcmsit kernel: lowmem_reserve[]: 0 0 0 0
Jul 3 03:53:04 rcmsit kernel: Node 0 DMA: 17*4kB (UEM) 1*8kB (E) 2*16kB (UE) 2*32kB (EM) 2*64kB (UM) 2*128kB (UE) 2*256kB (UE) 2*512kB (EM) 3*1024kB (UEM) 1*2048kB (E) 2*4096kB (M) = 15404kB
Jul 3 03:53:04 rcmsit kernel: Node 0 DMA32: 6745*4kB (UEM) 1058*8kB (UEM) 922*16kB (UEM) 546*32kB (UEM) 208*64kB (UEM) 83*128kB (UEM) 24*256kB (UEM) 13*512kB (UEM) 4*1024kB (U) 0*2048kB 0*4096kB = 108500kB
Jul 3 03:53:04 rcmsit kernel: Node 0 Normal: 682*4kB (UEM) 180*8kB (UEM) 151*16kB (UEM) 39*32kB (UEM) 18*64kB (UEM) 9*128kB (UEM) 3*256kB (UEM) 2*512kB (EM) 0*1024kB 0*2048kB 0*4096kB = 11928kB
Jul 3 03:53:04 rcmsit kernel: Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB
Jul 3 03:53:04 rcmsit kernel: 143267 total pagecache pages
Jul 3 03:53:04 rcmsit kernel: 7945 pages in swap cache
Jul 3 03:53:04 rcmsit kernel: Swap cache stats: add 284622, delete 276677, find 2373345/2374779
Jul 3 03:53:04 rcmsit kernel: Free swap = 0kB
Jul 3 03:53:04 rcmsit kernel: Total swap = 1023996kB
Jul 3 03:53:04 rcmsit kernel: 1048476 pages RAM
....
Jul 3 03:53:04 rcmsit kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
Jul 3 03:53:04 rcmsit kernel: [ 553] 0 553 26364 23 51 223 -1000 sshd
Jul 3 03:53:04 rcmsit kernel: [10530] 27 10530 452707 79498 396 91599 -1000 mysqld
Jul 3 03:53:04 rcmsit kernel: [30398] 0 30398 582767 20612 109 62 0 java
Jul 3 03:53:04 rcmsit kernel: [ 515] 0 515 35253 2031 17 4 0 redis-server
Jul 3 03:53:04 rcmsit kernel: [ 520] 0 520 35253 1816 17 1 0 redis-server
Jul 3 03:53:04 rcmsit kernel: [ 524] 0 524 35253 1878 17 1 0 redis-server
Jul 3 03:53:04 rcmsit kernel: [ 5404] 0 5404 612065 43032 228 0 0 java
Jul 3 03:53:04 rcmsit kernel: [16265] 0 16265 520357 65939 261 0 -1000 java
Jul 3 03:53:04 rcmsit kernel: [ 3251] 0 3251 1005500 235844 563 0 -1000 java
Jul 3 03:53:04 rcmsit kernel: [ 5709] 1000 5709 1007769 134443 358 0 0 java
Jul 3 03:53:04 rcmsit kernel: [ 9434] 0 9434 142807 793 38 0 0 YDService
Jul 3 03:53:04 rcmsit kernel: Out of memory: Kill process 5709 (java) score 109 or sacrifice child
Jul 3 03:53:04 rcmsit kernel: Killed process 5709 (java) total-vm:4031076kB, anon-rss:537772kB, file-rss:0kB, shmem-rss:0kB

    在03:53的时候,看到oom_killer被唤醒。oom_killer会选择占用内存最高的,然后kill 掉 oom_score_adj 值高的进程。
    日志显示,oom_killer 杀的进程号为5709,而5709是一个java进程,所以也可以断定就被是kill掉的tomcat ,它占用的内存为612065,oom_score_adj为0,所以oom_killer选择kill掉它。

插播 oom_killer介绍

    Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。
    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%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序
继续分析

    关闭进程选择策略:没用的且耗内存多的程序被枪,作为用户,我们可以通过设置一些值来影响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

解决
  1. 加内存,享受价格成倍增长的乐趣
  2. 修改/proc/pid/oom_adj,取值-17~15,默认0,越小越不容易选中
  3. 根据项目情况修改堆栈配置。(下文tomcat 调优)
  4. 修改代码,比如说大批量处理,不应该一开始把数据全加载到内存中。
  5. 多说一句:我发现每天固定有个时间点,内存会莫名升高,估计是腾讯云在跑批,所以内存不要用得太紧,多留一点。

tomcat启动慢

原因

    Tomcat 7/8使用org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom类产生安全随机类SecureRandom的实例作为会话ID,这个操作巨慢。

解决
  1. 在Tomcat环境中解决,可以通过配置JRE使用非阻塞的Entropy Source。在catalina.sh中加入这么一行:
    1
    -Djava.security.egd=file:/dev/./urandom
  2. 在JVM环境中解决,打开$JAVA_PATH/jre/lib/security/java.security这个文件
    1
    2
    3
    securerandom.source=file:/dev/urandom
    替换成
    securerandom.source=file:/dev/./urandom
/dev/urandom 和 /dev/random

    /dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。很多解密程序与安全应用程序(如SSH Keys,SSL Keys等)需要它们提供的随机数据流。
    这两个设备的差异在于:
    /dev/random的random pool依赖于系统中断,因此在系统的中断数不足时,/dev/random设备会一直封锁,尝试读取的进程就会进入等待状态,直到系统的中断数充分够用.
    /dev/random设备可以保证数据的随机性。
    /dev/urandom不依赖系统的中断,也就不会造成进程忙等待,但是数据的随机性也不高。

tomcat 调优

1
JAVA_OPTS="-Xms512m -Xmx2048m -XX:PermSize=256M -XX:MaxPermSize=512m -Ddubbo.shutdown.hook=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/administrator/software/server/tomcat-rcm/dump/oom.hprof"

    以上为2核4G的配置,只做参考,根据自己项目修改,在catalina.sh加上。

    Xmx:用来设置你的应用程序能够使用的最大内存数,如果程序要花很大内存的话,那就需要修改增加此数的值。
    Xms:用它来设置程序初始化的时候内存栈的大小,增加这个值的话你的程序的启动性能会得到提高。
    XX:PermSize:表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)
    XX:MaxPermSize:表示对非堆区分配的内存的最大上限
    XX:+HeapDumpOnOutOfMemoryError:JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”,并将其保存在一个文件中。

    JAVA_OPTS这个变量会在tomcat启动时应用,但是也会在执行shutdown.sh时应用。当你关闭tomcat时如果告诉你内存不够,不要惊慌,这只是在执行shutdown.sh会启动另一个程序,而这时申请不到像启动时那么大的内存,就会报错,你可以忽略该异常,执行kill来关闭tomcat。

tomcat访问项目时不需要加项目名称

    修改conf目录下的server.xml配置

1
2
3
4
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">  
<!-- path留空代表访问域名后面不需要带项目的名称 -->
<Context path="" docBase="F:\temp" reloadable="false" />
</Host>