由于想体验新版的Lambda和Stream API,把服务器上的JRE换成了Java 8.而之后网站偶尔出现无法访问,java进程莫名奇妙地退出。怀疑是虚拟机内存溢出,但tomcat与应用的日志都没有崩溃的相关日志输出。

经过几次在线监控,发现java进程占用超过系统50%的内存。首先,网站应该占用不了这么多的内存,虽然我没有用-Xmx指定heap内存大小,但按规范,默认应该分配是物理内存的1/4。其它的Permgen区域,也应该无法使用这么多的内存。

这点我先放了放。猜测linux会不会自动杀死占用内存过多的进程? (没有太多的linux维护经验,也没有系统的学习过linux)。 结果在linux syslog中发现了这段。

#oom killer
kernel: java invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0

按关键字google一下,查到了linux的oom-killer机制:linux会在内存紧张时,杀死占用太多内存的进程,以保证系统正常运行。

查到这个原因后便开始分析内存使用的情况。
由于是在linux上,惯手的内存工具缺失,jmap运行很久也拿不到内存dump(吐个嘲先,我用jmap导出内存dump,一个多小时都没有导出来,而且期间jvm会处于假死状态,网站无法访问)。 就打算用jstat来看下各个段的大致使用情况(主要使用jstat -gcutil/jstat -gccapacity/jmap -heap)。

发现Permgen段使用量为97%,而且已经使用了120多M了 (其实当时jstat中的标记是M:指代Java8中的Metaspace,而Permgen标记应该为P,这犯了个傻,误认为是PermGen了)

联想到我大量使用了groovy来实现不停机发布。 怀疑是不是groovy没有正确的卸载无用的class? 我尝试改小-XX:MaxPermSize,期待能够看到Permgen的内存溢出。结果在启动时发现java 8已经不支持MaxPermSize参数了,整个Permgen段都已经被移除了。 而是使用一个叫Metaspace的段。

Metaspace capacity
By default class metadata allocation is limited by the amount of available native memory (capacity will of course depend if you use a 32-bit JVM vs. 64-bit along with OS virtual memory availability).
A new flag is available (MaxMetaspaceSize), allowing you to limit the amount of native memory used for class metadata. If you don’t specify this flag, the Metaspace will dynamically re-size depending of the application demand at runtime.

而且这个段默认是无限增长!最大值为实际的可用物理内存。而且这个段触发GC的条件是达到MaxMetaspaceSize。这也解释了为什么程序占用了50%还多的内存。 之后通过限制MetaspaceSize使其正确的执行gc,避免占用内存过多而被linux杀掉。

—————————————–
关于oom-killer

可以通过/proc/sys/vm下的


#进程请求分配内存的限制

overcommit_memory=<option>

#0-允许进程轻微过量使用内存,但对于大量过载请求则不允许

#1-永远允许进程overcommit,尝试分配请求内存,而不评估内存是否足够,直致报错。

#2-允许时程overcommit,其限制为overcommit_ratio设置了比例

# 将 overcommit_memory 设定为 2 时,将overcommit的值限制为物理RAM+Swap比例。默认为 50。

overcommit_ratio=<ratio>

#内存紧张时(OOM)执行Kill进程的动作选项配置

oom_kill_allocating_task=<option>

#0-杀死导致oom的进程

#1-扫描并杀死内存最多的进程

oom_dump_tasks=<option>

#0-触发oomkill时,不输出进程相关信息
#1-反之

panic_on_oom=<option>
#0-oom时调用oom_kill()杀死进程
#1-oom时,系统panic(抛出内部错误,类似于Java中的ERROR, 表示Linux无法处理或者恢复解决panic)