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

版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/194.html

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

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

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

我定位有可能造成的原因是以上原因,针对本站的特点在做细排查,有可能出现 的问题。

  1. io流操作文档没关闭流。(有可能)
  2. 往一个静态集合变量里一直压栈。(没有这个问题)
  3. 链接没释放。(有可能,因为本站有大量的HttpClient请求)
  4. Java队列没消耗。(有可能,因为本站使用上了)
  5. Ehcache缓存使用量过大。(没使用)
  6. 频繁IO操作大文件。(没有)
  7. Session过期时间太久。(可能有)
  8. 等等.....

二、采用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流操作没关闭检查(严重)

全局搜索各种InputStreamOutputStream ,各种Buffer 等等,然后各种修改关闭。尤其是本站的 HTTP 模拟请求工具,一天的用量非常大。如下IO 流在finallytry...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.4 Java队列(最终问题定位)

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

换成了阿里的 队列 ,我把队列用于本地计算机跑,线上跑网站,把所有队列的错误信息,以及执行情况看了一下,发现是之前逻辑写的有问题,导致队列异常,队列异常没有catch及时处理。故导致了这个现象的最大的罪魁祸首。

转载声明:本文转载自「成猿之路」,搜索「softwareload」即可关注。

原文发布于微信公众号 - 成猿之路(softwareload)

原文发表时间:2018-05-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

Kali下常用安全工具中文参数说明(160个)

*本文原创作者:屌丝绅士,属Freebuf原创奖励计划,转载请注明来自FreeBuf 由于篇幅有限,只列举部分,ps:第一次发有什么不对的 还望各位大大指正 n...

1K9
来自专栏安恒网络空间安全讲武堂

Python编写渗透工具学习笔记二 | 0x02利用FTP与web批量抓肉鸡

0x02利用FTP与web批量抓肉鸡 脚本要实现的目标和思路: 先尝试匿名登录ftp,当匿名登录失败时再尝试用用户/密码爆破登录,登录成功后,脚本会搜索ftp中...

1.7K7
来自专栏Java进阶架构师

9个提升逼格的redis命令

既然keys命令不允许使用,那么有什么代替方案呢?有!那就是scan命令。如果把keys命令比作类似select * from users where user...

1314
来自专栏轮子工厂

97 条 Linux 运维工程师常用命令总结 | 史上最全Linux命令总结

先说明一下,这篇文章只是一篇常用基础命令的汇总,小白可以看着学习一下,对大牛帮助不大。。。。

1682
来自专栏Java技术栈

分布式 | Dubbo 架构设计详解

Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度...

1632
来自专栏CSDN技术头条

一组 Redis 实际应用中的异常场景及其根因分析和解决方案

在上一场 Chat《基于 Redis 的分布式缓存实现方案及可靠性加固策略》中,我已经较为全面的介绍了 Redis 的原理和分布式缓存方案。如果只是从“会用”的...

3623
来自专栏阿杜的世界

【转】Dubbo架构设计详解总体架构核心要点参考资料

Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度...

1155
来自专栏北京马哥教育

linux服务器下LNMP安装与配置方法

云豆贴心提醒,本文阅读时间6分钟 一、准备 1.准备php函数的rpm包 2.准备lnmp其他的源代码包 3.安装php-5.2.14源代码包所需要的函数支...

3729
来自专栏静晴轩

浅谈android中的目录结构

之前在android游戏开发中就遇到本地数据存储的问题:一般情形之下就将动态数据写入SD中存储,在没有SD卡的手机上就需另作处理了;再有在开发android应用...

34610
来自专栏名山丶深处

springboot集成schedule(深度理解)

6505

扫码关注云+社区

领取腾讯云代金券