您的位置:首页 > 运维架构 > Tomcat

关于Java Tomcat 内存溢出排查心得分享

2017-04-10 12:28 323 查看
我网站不知道什么时候,开始内存飙升,从   Tomcat  启动后,初始内存占用
4%~5%
 左右,到
20%、40%
 最后服务器卡死,SSH都连不上服务器,不得不重启。但是我知道是我程序的问题。然后分析问题,解决问题。陆陆续续持续了一个多月,下面分享解决思路。


一、定位造成内存溢出可能存在的问题

io流操作文档没关闭流。
往一个静态集合变量里一直压栈。
连接没释放。
Java队列没消耗。
Ehcache缓存使用量过大。
频繁IO操作大文件。
Session过期时间太久。
等等.....

我定位有可能造成的原因是以上原因,针对本站的特点在做细排查,有可能出现的问题。
io流操作文档没关闭流。(有可能)
往一个静态集合变量里一直压栈。(没有这个问题)
链接没释放。(有可能,因为本站有大量的HttpClient请求)
Java队列没消耗。(有可能,因为本站使用上了)
Ehcache缓存使用量过大。(没使用)
频繁IO操作大文件。(没有)
Session过期时间太久。(可能有)
等等.....


二、采用Memory Analyzer Tool(MAT)分析Java内存

采用   jmap  命令(Java Memory Map)导出内存转储快照(Dump);

首先查询到你对应的   Tomcat  的pid

ps -aux|grep xxx-tomcat




然后执行jmap命令:

jmap -dump:format=b,file=73630.hprof 16706




导出完毕。down下来用   Eclipse  ,或者   MyEclipse  查看,但是 
MyEclipse
 或者 
Eclipse
 要先安装工具,自行百度。然后以
openFile
的方式打开。如图:



可能有点看不懂,自行解决,点击
Histogram
 ,可以看到内存中的详细信息。



可以看到
char[]
 、
byte[]
 占用的是最多,而且不是多一点点。这明显不正常。就是一些IO流相关的信息。Memory Analyzer
工具还是有很多功能的,我也不太会用。具体可以多看看相关的博客。下面来排查问题。


三、问题逐一排查,由容易到复杂


3.1 Session检查

从配置文件
web.xml
 查看,发现   Session    超时配置了
900
 分钟。。

。醉了,回想起来,是当时因为有权限校验(防止攻击)模块利用
  Session  来实现,所以才出此下策。改成
30
 分钟,重启后效果有一点点。继续排查。


3.2 IO流操作没关闭检查(严重)

全局搜索各种
InputStream
 、
OutputStream
 ,各种
Buffer
 等等,然后各种修改关闭。尤其是本站的  HTTP 
模拟请求工具,一天的用量非常大。如下
IO
 流在
finally
 里
try...catch
 各种关闭。

try{

//......

}catch(Exception e){

//......

}finally{

realUrl = null;

try {

if(null != conn)conn.disconnect();

conn = null;

} catch (Exception e2) {

LoggerUtils.fmtError(HttpManager.class, e2, "请求完毕关闭流出现异常,可以忽略![%s]", url);

}

try {

if(null != outStream)outStream.close();

outStream = null;

} catch (Exception e2) {

LoggerUtils.fmtError(HttpManager.class, e2, "请求完毕关闭流出现异常,可以忽略![%s]", url);

}

try {

if(null != out)out.close();

out = null;

} catch (Exception e2) {

LoggerUtils.fmtError(HttpManager.class, e2, "请求完毕关闭流出现异常,可以忽略![%s]", url);

}

try {

if(null != inStream)inStream.close();

inStream = null;

} catch (Exception e2) {

LoggerUtils.fmtError(HttpManager.class, e2, "请求完毕关闭流出现异常,可以忽略![%s]", url);

}

try {

if(null != in)in.close();

in = null;

} catch (Exception e2) {

LoggerUtils.fmtError(HttpManager.class, e2, "请求完毕关闭流出现异常,可以忽略![%s]", url);

}

double end = System.currentTimeMillis();

map.put("time", (end - begin) / 1000);

//大对象用完赋值null

bo = null;//促进回收

}


结论:全部加好后重启,过一段时间再看。效果不明显。其实是我在平时代码严谨上这个错误没有出现,但是从经验角度来说,如果这个没处理好,这个是最容易出现 内存溢出  的。

ps:关于后面有一段代码,
bo=null;//促进回收
 ,我个人是这么理解,不知道有没毛病。主要是针对局部变量的大变量。可以用完后赋值为
null
 。


3.3 HttpClient请求链接释放问题(严重)

  Httpclent  请求链接不主动关闭,这个问题也是个大问题,但是对内存的影响,看从什么角度,占用最大的应该还是响应链接,把链接用完了,新的链接就进不来,我们知道  Tomcat  默认配置一共好像才
150
 个,一会就用完了,如果不用完关闭,那么会造成链接释放慢,甚至不释放。如果不释放,请求得到的
responseBody
 那么有可能一直没有释放了。

HttpClient怎么释放?

其实百度一下有很多答案,我这里顺便带一下。


1.请求头增加关闭Head信息。

//添加头信息

HttpURLConnection conn = null;

URL realUrl = new URL(url);

// 打开和URL之间的连接

conn = (HttpURLConnection) realUrl.openConnection();

//省略部分代码

//增加请求完毕后关闭链接的头信息

conn.setRequestProperty("Connection", "close");



2.用完Httpclent后手动关闭

//添加头信息

HttpURLConnection conn = null;

URL realUrl = new URL(url);

// 打开和URL之间的连接

conn = (HttpURLConnection) realUrl.openConnection();

//省略部分代码

//手动释放

if(null != conn)conn.disconnect();



3.通过线程的方式扫描关闭。

这个方法其实类似启动一个守护线程(一直启动着),来扫描有没有关闭的请求。这个方法比较鸡肋,用的好就很好,用的不好就蛋痛了。推荐使用方法一、方法二,为了保险起见你可以两种一起使用,并不会有问题。

总结:  Httpclent  链接请求完毕一定要关闭。有的人可能会看了浏览器里的请求头信息为:
Connection:keep-alive
 ,这个回头我会详细说明,但是这个是浏览器需要的,因为你还要继续加载
css、js、image
 等等,大概是这个意思,而你的  Httpclent  只需要加载一次,所以直接
close
 即可。


3.3 Java队列(最终问题定位)

昨晚把  队列  换成了阿里的队列,问题解决了,几个小时过去了,还是
5.6%
 。


换成了阿里的  队列  ,我把队列用于本地计算机跑,线上跑网站,把所有队列的错误信息,以及执行情况看了一下,发现是之前逻辑写的有问题,导致队列异常,队列异常没有catch及时处理。故导致了这个现象的最大的罪魁祸首。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: