weblogic内存过大排查
背景
某项目weblogic AdminServer在启动后会慢慢消耗内存,并且超过-Xmx
设置的大小,最终消耗完整个服务器的内存。
- 内存使用情况
[aaa@qq.com ~]# free -m
total used free shared buff/cache available
Mem: 31996 30878 229 195 888 358
Swap: 8063 5793 2270
- 各进程使用内存情况
Tasks: 355 total, 1 running, 354 sleeping, 0 stopped, 0 zombie
%Cpu(s): 7.4 us, 1.3 sy, 0.0 ni, 91.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32763912 total, 229628 free, 31621300 used, 912984 buff/cache
KiB Swap: 8257532 total, 2327596 free, 5929936 used. 364928 avail Mem
Unknown command - try 'h' for help
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
128235 oracle 20 0 24.8g 14.9g 6272 S 94.1 47.7 2390:44 java
2951 oracle 20 0 800088 71008 480 S 17.6 0.2 4581:11 gsd-color
101629 root 20 0 162236 2372 1532 R 5.9 0.0 0:00.03 top
......
进程128235
就是Admin Server,显示已经消耗47.1%的内存,并且CPU很高,cpu问题猜测可能是内存不足导致的并发症。
预备知识
java内存
一个jvm进程消耗的物理内存不单单-Xmx设置的大小,-Xmx只是设置了堆(heap)内存的大小,也就是java代码运行时各种变量,数据保存的地方,堆内存里面又分了年轻代,老年代,持久代,这个就不展开,除了堆以外,还包括PermGen(永久代)和线程堆栈,对应的启动参数为-XX:MaxPermSize
和-Xss
,Xss默认是1M,MaxPermSize没有默认值,因此正常情况下,是需要设置MaxPermSize,简而言之,jvm消耗内存公式为:
Max memory = [-Xmx] + [-XX:MaxPermSize] + 线程数 * [-Xss]
除此之外,我们知道,java可以通过JNI
调用本地程序,本地程序可以是C/C++或者汇编等,那么在本地程序中申请的内存jvm是无法限制,因此如果有JNI调用的情况下
Max memory = [-Xmx] + [-XX:MaxPermSize] + 线程数 * [-Xss] + JNI内存
注意
JDK版本从1.8后,不在支持MaxPermSize和PermSize,替换为另外两个参数MaxMetaspaceSize
和MetaspaceSize
,这两个并不等同,但作用类似,PermSize是固定内存,MetaspaceSize是动态增长
RES
在top命令中,进程列表有个参数RES(resident memory usage)
,表示常驻内存,比如程序申请了100M,实际只使用了10M,那么RES就是10M,所以这个代表应用程序真正使用的物理内存,在上面的top命令中显示PID 128235使用的物理内存为14.9g
,这个显然是不正常的。
pmap
pmap命令可以显示进程的内存信息
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- java
0000000000600000 4 4 4 rw--- java
00000000012e5000 132 12 12 rw--- [ anon ]
00000000c0000000 294400 209860 209860 rw--- [ anon ]
00000000d1f80000 55808 0 0 ----- [ anon ]
00000000d5600000 174080 0 0 rw--- [ anon ]
00000000e0000000 349184 349184 349184 rw--- [ anon ]
00000000f5500000 175104 174204 174204 rw--- [ anon ]
- Address:内存地址(这里是虚拟地址,不是物理内存地址)
- Kbytes:内存块大小,这里是虚拟内存大小,不一定真正使用这么大
- RSS:常驻内存大小,和top的RES一样
- Dirty:脏页大小
- Mode:读写模式
- Mapping:映像支持文件,[anon]为已分配内存 [stack]为程序堆栈
我们重点关注RSS
这个参数
gdb
gdb是Linux上面自带的应用程序调试工具,功能强大,我们可以借助这个工具将内存dump出来查看。
排查
用jps -v命令查看jvm启动参数
# jps -v|grep AdminServer
128235 Server -Xms1024m -Xmx4096m -Djava.security.egd=file:/dev/./urandom -Dlaunch.use.env.classpath=true -Dweblogic.Name=AdminServer -Djava.security.policy=/u01/fmwhome/fmwinstall/wlserver/server/lib/weblogic.policy -Dweblogic.ProductionModeEnabled=true -Djava.system.class.loader=com.oracle.classloader.weblogic.LaunchClassLoader -Djava.protocol.handler.pkgs=oracle.mds.net.protocol|com.bea.wli.sb.resources.url|oracle.fabric.common.classloaderurl.handler|oracle.fabric.common.uddiurl.handler|oracle.bpm.io.fs.protocol -Dopss.version=12.2.1.3 -Digf.arisidbeans.carmlloc=/u01/fmwhome/fmwcfg/user_projects/domains/soa_domain/config/fmwconfig/carml -Digf.arisidstack.home=/u01/fmwhome/fmwcfg/user_projects/domains/soa_domain/config/fmwconfig/arisidprovider -Doracle.security.jps.config=/u01/fmwhome/fmwcfg/user_projects/domains/soa_domain/config/fmwconfig/jps-config.xml -Doracle.deployed.app.dir=/u01/fmwhome/fmwcfg/user_projects/domains/soa_domain/servers/AdminServer/tmp/_WL_user -Doracle.deployed.app.ext=/- -Dweblogic.alternateTypesDi
设置了最大堆内存-Xmx4096m
,千万别以为设置了Xmx就万事大吉了。
用pmap查看内存(内容太多,这里只截取最后几列)
# pmap -x 128235 | sort -n -k3
00007fb570000000 65524 54428 54428 rw--- [ anon ]
00007fb730000000 65508 54564 54564 rw--- [ anon ]
00007fb6d8000000 65520 54748 54728 rw--- [ anon ]
00007fb670000000 65524 54908 54904 rw--- [ anon ]
00007fb6fc000000 65532 55036 55036 rw--- [ anon ]
00007fb608000000 65532 55776 55764 rw--- [ anon ]
00007fb664000000 65536 56072 56072 rw--- [ anon ]
00007fb6b8000000 65528 56916 56916 rw--- [ anon ]
00007fb6cc000000 65516 58632 58592 rw--- [ anon ]
00007fb48c000000 65132 58968 58968 rw--- [ anon ]
00007fb6d0000000 65512 60472 60440 rw--- [ anon ]
00007fb6f0000000 65512 62652 62652 rw--- [ anon ]
00007fb6f8000000 65528 63628 63600 rw--- [ anon ]
00007fb6ec000000 65516 63956 63936 rw--- [ anon ]
00007fb6e4000000 65360 64652 64616 rw--- [ anon ]
00007fb6e0000000 65536 64692 64668 rw--- [ anon ]
00007fb710000000 65512 64804 64804 rw--- [ anon ]
00007fb6e8000000 65536 64852 64824 rw--- [ anon ]
00007fb6f4000000 65532 65052 65048 rw--- [ anon ]
00007fb6dc000000 65508 65464 65452 rw--- [ anon ]
00000007c0000000 90368 89484 89484 rw--- [ anon ]
00007fb718000000 131072 90160 90160 rw--- [ anon ]
0000000000a34000 141928 91096 91096 rw--- [ anon ]
00007fb75d000000 201344 200160 200144 rwx-- [ anon ]
000000076ab00000 1397760 1372188 1372188 rw--- [ anon ]
00000006c0000000 2796544 2765312 2765312 rw--- [ anon ]
total kB 25893432 15534700 15529228
最后5块内存应该对应的是堆内存
00007fb718000000 131072 90160 90160 rw--- [ anon ]
0000000000a34000 141928 91096 91096 rw--- [ anon ]
00007fb75d000000 201344 200160 200144 rwx-- [ anon ]
000000076ab00000 1397760 1372188 1372188 rw--- [ anon ]
00000006c0000000 2796544 2765312 2765312 rw--- [ anon ]
加起来差不多是4G,那么除了这些,存在大量64M(65536)的内存块,正是这些64M内存块大量消耗内存,那么这里面到底存了什么内容。
我们可以选取一块内存快用gdb工具查看,比如00007fb6e0000000
,gdb要求指定开始地址和结束地址,这里只有开始地址结束地址怎么计算?只要开始地址加上64M就行,64M=65536KB=67108864B=0x4000000B。那么:
结束地址 = 0x7fb6e0000000 + 0x4000000 = 0x7fb6e4000000
- gdb导出内存
# gdb attach 128235
> dump memory /root/dump.bin 0x7fb6e0000000 0x7fb6e4000000
/root/dump.bin
就是dump出来的内存文件,内存里可能是二进制也可能是字符串,二进制基本很难分析,但能从字符串看出一些线索,64M不算大,稍微好一点的文本编辑器都能打开(千万不要用记事本打开),打开看之后发现大量字符内容都是类签名信息和看起来像是jar中MANIFEST.MF文件内容
导出其他内存块也是同样的结果,jvm中类信息和meta信息都是存在在永久代中,也就是PermGen空间里,因此猜测很可能是PermGen未设置导致无节制增长,从而将内存消耗光。
解决并验证
在AdminServer上添加600M PermGen的限制(具体怎么添加下面有专门的章节介绍)后观察内存变化,为了尽快看到效果,这边做了几件事
- 通过以下命令在控制台上持续打印内存变化情况
for i in {1..100000}; do ps -p 25725 -o rss;sleep 3s; done
- 通过以下命令在控制台上持续打印GC情况(主要是观察FGC情况)
[oracle]
jstat -gcutil 25725 1000 10000
-
登录console,不断对应用进行部署取消动作,可以让内存持续增长,因为部署应用weblogic会将包信息加载至PermGen。
-
以下是在加了600M PermGen限制后的内存,gc变化情况
刚开始内存一直增长,但增长到大约2960000KB的时候开始进行FGC,此后内存消耗稳定在这个水平
按照预测内存应该稳定在2048M+600M=2648M=2711552KB上下,这边多了大概200M左右的内存消耗,可能是线程堆栈,但不确定,有兴趣可以去研究,这不影响本次问题的诊断
接着我们解除了PermGen限制,重启了AdminServer,然后做同样的事,以下是结果的截图:
我们可以看到有7次的FGC,但实际上,weblogic启动过程中已经FGC 7次了,所以后面模拟部署的过程其实一次FGC也没执行,也就是,根本没进行垃圾回收。通过不断的部署,内存消耗很快就超过了3G,并完全没有降的情况,因此我们可以得出结论
AdminServer承担着应用部署的任务,如果没有设置PermGen的大小,AdminServer内存的消耗会随着部署次数的增加不断的增长,最终将整个操作系统内存消耗殆尽
weblogic如何配置参数
之所以专门一个章节讲这个,原因是每个项目有每个项目的方法,有直接修改setDomainEnv.sh
脚本的,有通过额外脚本setStartupEnv.sh
设置,有通过console设置,五花八门的方法都有,但有些方法虽然能达到目的,但我觉得不是最佳实践,存在问题。因此这里推荐一种最佳实践方法。
在console,点击server->配置->服务器启动
,有个参数的文本输入框,这里可以配置weblogic的启动参数
比如要配置内存参数可以参考
-Xms8192m -Xmx8192m -XX:MaxPermSize=1024m -XX:PermSize=1024m
JDK>=1.8需要将MaxPermSize
改为MaxMetaspaceSize
,PermSize改为MetaspaceSize
在console上配置的好处:
- 查看方便,修改方便
- 可视化傻瓜式配置
在脚本里修改,有时候涉及的地方很多,都要修改,而且并不是所有开发人员都了解Linux,都了解shell脚本,所以不建议修改脚本的方式。
注意!!!!,这里配置的参数,只能通过nodemanager启动才能生效,也就是只有通过console启动服务器,才能生效,你手动在后台通过脚本启动是无法生效的,所以对于manager server,这样配是可以的,但是 AdminServer一般都是手动在后台启动的,所以怎么办呢?当然,你可以再搭建一台专门用来启动AdminServer的weblogic(真的有客户这么做的),这个成本有点高,但从架构上确实是合理的,对于AdminServer,我们可以修改setDomainEnv.sh
脚本。
由于不同版本的weblogic setDomainEnv.sh脚本内容不一样,因此没有统一改的位置,可以通过以下方法找到修改的位置
找到脚本里最后一次出现export MEM_ARGS
的位置,然后在后面添加以下内容
if [ "${SERVER_NAME}" = "AdminServer" ] ; then
MEM_ARGS="${MEM_ARGS} -XX:MaxMetaspaceSize={你希望调整的大小(单位:兆)}m -XX:MetaspaceSize={你希望调整的大小(单位:兆)}m"
export MEM_ARGS
fi
注意!!!!,如果是JDK<=1.7或者jrokit,那么使用以下脚本
if [ "${SERVER_NAME}" = "AdminServer" ] ; then
MEM_ARGS="${MEM_ARGS} -XX:MaxPermSize={你希望调整的大小(单位:兆)}m -XX:PermSize={你希望调整的大小(单位:兆)}m"
export MEM_ARGS
fi
如果是JDK>=1.8,那么使用以下脚本
if [ "${SERVER_NAME}" = "AdminServer" ] ; then
MEM_ARGS="${MEM_ARGS} -XX:MaxMetaspaceSize={你希望调整的大小(单位:兆)}m -XX:MetaspaceSize={你希望调整的大小(单位:兆)}m"
export MEM_ARGS
fi