欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

weblogic内存过大排查

程序员文章站 2022-04-28 21:26:28
...

背景

某项目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,替换为另外两个参数MaxMetaspaceSizeMetaspaceSize,这两个并不等同,但作用类似,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的启动参数

weblogic内存过大排查weblogic内存过大排查正在上传…重新上传取消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脚本内容不一样,因此没有统一改的位置,可以通过以下方法找到修改的位置

weblogic内存过大排查

找到脚本里最后一次出现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
相关标签: weblogic