在TOMCAT WEB程序的运行过程中,突然触发了内存溢出错误,检查Tomcat的localhost日志,找到如下信息:

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:242)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
at java.util.ArrayList.add(ArrayList.java:440)
at com.mysql.jdbc.MysqlIO.readSingleRowSet(MysqlIO.java:3442)
……
at com.yiifaa.indicator.impl.HoleIndicatorServiceImpl.expireHoles(HoleIndicatorServiceImpl.java:191)
at com.yiifaa.indicator.controller.HoleIndicatorController.expireHoles(HoleIndicatorController.java:69)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

按照提示,找到代码进行检阅:

Map<String, Object> params = Maps.newHashMap();
String sql = sqlCache.get("expireHoles");
// 这里就是191行
List<Map<String, Object>> results = this.jdbcTemplate.queryForList(sql, params);
  • 1
  • 2
  • 3
  • 4

看了好几遍,没有发现明显的异常,既没有全局变量,也没有属性变量,没理由出现内存溢出,更重要的是,按照以往的经验,SQL应该没有这么大的爆炸力,而且难以确定的是,到底是该方法引发了OOM异常,还是碰巧就遇到了内存的瓶颈,从而导致内存异常,所以需要进一步判断。

刷新页面,调用应用程序接口,同时使用jstat监测JVM内存使用,如下:

# 首先获取程序ID
jps -l
# 然后根据程序ID获取其JVM内存使用信息
# 10代表时间间隔,毫秒
jstat -gcutil 47279 10
  • 1
  • 2
  • 3
  • 4
  • 5

执行jstat命令后,发现如表1的现象:

S0 S1 E(年轻代占比) O(老年代占比) P(永久代占比)
不变 持续增长 持续增长 持续增长 不变

增长的顺序依次是E(年轻代占比)增长到100%,然后S1增长到100%,然后O(老年代占比)增长到100%,最后触发full gc(全局垃圾回收),并且在内存变化的这段时间,并没有进行任何操作,连接数量也在比较小的范围。

#   统计HTTP连接的活动线程
jstack 47279|grep http-bio-8080-exec|grep runnable|wc -l
# 统计HTTP连接数量
netstat -nat|grep -i '8080'|wc -l
  • 1
  • 2
  • 3
  • 4

上述两个命令相互论证,证明HTTP连接是正常的,继续用jstack进行诊断,发现很多HTTP线程处于“WAITED”状态,这说明都是处于连接池中,等待被激活,依旧正常。

继续推测,内存的增长必然伴随对象数量的增加,于是采用jmap进行诊断,如下:

jmap -histo 47279|less 
  • 1

持续多次运行此命令,发现有个对象数量以及增长速度都不正常,如下:

1716115  41187603  com.mysql.jdbc.ByteArrayRow
  • 1

难道真是数据库连接的问题?突然想到,消耗内存较多的代码一定是运行中的线程,于是用jstack获取到的信息依次诊断线程,发现真有个数据库线程不正常,如下:

http-bio-8080-exec-89" daemon prio=10 tid=0x00007f039c09f800 nid=0xbcd1 runnable [0x00007f05350cc000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:152)
at java.net.SocketInputStream.read(SocketInputStream.java:122)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)
……
at com.yiifaa.sec.indicator.impl.HoleIndicatorServiceImpl.expireHoles(HoleIndicatorServiceImpl.java:191)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

发现程序依旧卡在HoleIndicatorServiceImpl类的191行,最后诊断SQL语句,发现少了一个括号,导致or语句连接数量增长极快,从而是数据量达到了10的22次方,最后引发了如此大的执行灾难。

继续细化分析下去,因为返回结果是个List容器,所以随着游标的滑动,导致数据集不断增长,从而使得“com.mysql.jdbc.ByteArrayRow”对象数量暴增,最后导致内存溢出,结合内存的增长趋势,可以明显看出,当方法内部的对象增大到一定程度时,将会直接进入幸存代(S1),甚至是老年代。

总结

从内存的增加现象看: 
1. 只要对象足够大,完全有可能直接进入老年代,即使实在局部方法内; 
2. 局部方法也可能会导致内存溢出;

从诊断过程看,相关工具的作用如下: 
1. 用jps获取jvm进程ID; 
2. 用jstat确认GC内存消耗速度; 
3. 用jmap确认对象的增长速度与耗费空间; 
4. 用jstack确认错误代码的位置。

从SQL的教训看: 
1. 在多表连接时,请仔细检查连接语句; 
2. 万无一失的做法,请限定抓取数据的数量,例如100条,如“limit 0, 100”。

原文地址i:https://blog.csdn.net/yiifaa/article/details/78648358

一起SQL引发OOM的解决思路与过程(转载)的更多相关文章

  1. 【OOM】解决思路

    一.什么是OOM? OOM就是outOfMemory,内存溢出!可能是每一个java人员都能遇到的问题!原因是堆中有太多的存活对象(GC-ROOT可达),占满了堆空间. 二.怎么解决? 1.拿到内存溢 ...

  2. MySQL在并发场景下的问题及解决思路

    目录 1.背景 2.表锁导致的慢查询的问题 3.线上修改表结构有哪些风险? 4.一个死锁问题的分析 5.锁等待问题的分析 6.小结 1.背景 对于数据库系统来说在多用户并发条件下提高并发性的同时又要保 ...

  3. 大数据小视角5:探究SSD写放大的成因与解决思路

    笔者目前开发运维的存储系统的服务器都跑在SSD之上,目前单机服务器最大的SSD容量有4T之多.(公司好有钱,以前在实验室都只有机械硬盘用的~~)但SSD本身的特性与机械硬盘差距较大,虽然说在性能上有诸 ...

  4. java spring 等启动项目时的异常 或 程序异常的解决思路

    今天搭建ssm项目的时候,因为pagehelper的一个jar包没有导入idea的web项目下的lib目录中,异常报错找不到pagehelper,这个问题在出异常的时候疯狂crash,让人心情十分不舒 ...

  5. DG日志不应用,GAP,主备切换解决思路与办法

    环境ORACLE 10G OS WINDOWS 对于DG故障解决思路,DG日志切换不进行应用,DG出现GAP解决方法,DG主备库切换, 当DG出现故障时,第一时间检测alert日志,服务器OS日志,网 ...

  6. ADO.NET 使用DELETE语句批量删除操作,提示超时,删除失败,几种优化解决思路

    起因是如此简单的一句sql 提示:Timeout 时间已到.在操作完成之前超时时间已过或服务器未响应. 提供几种解决思路: 1.检查WHERE条件中字段是否已建索引 2.检查是否被其他表引用,引用表外 ...

  7. C#不用union,而是有更好的方式实现 .net自定义错误页面实现 .net自定义错误页面实现升级篇 .net捕捉全局未处理异常的3种方式 一款很不错的FLASH时种插件 关于c#中委托使用小结 WEB网站常见受攻击方式及解决办法 判断URL是否存在 提升高并发量服务器性能解决思路

    C#不用union,而是有更好的方式实现   用过C/C++的人都知道有个union,特别好用,似乎char数组到short,int,float等的转换无所不能,也确实是能,并且用起来十分方便.那C# ...

  8. java高并发解决思路

    一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构.性能的要求都很简单,随着互联网业务的不断丰富,网站 ...

  9. ArcMap基于Oracle出现sde.instances_util.check_instance_table_conflicts:: ORA-00942:表或视图不存在/table or view doesnot exist解决思路

    SDE环境:Oracle12C+ArcMap10.7+WinServer2012 出现问题情况: 1.SDE可以连接正常打开,但就是无法新建要素.导入要素等: 1)在根目录新建或导入要素,弹出提示: ...

随机推荐

  1. 【Java】 剑指offer(8) 用两个栈实现队列

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集  题目 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数append ...

  2. Quratz入门

    一:介绍 1.应用场景 基本上任何公司都会用到调度这个功能, 比如我们公司需要定期执行调度生成报表, 或者比如博客什么的定时更新之类的,都可以靠Quartz来完成.正如官网所说,小到独立应用大到大型电 ...

  3. 024 IDEA不能新建spring下的配置文件xml

    这个问题以前就遇到过,一直懒得解决,现在必须要用了,解决了一下,发现网上的解决方式果然不错. 1.现象 2.解决方式 需要添加一下spring context的依赖即可

  4. work工作消息队列Round-robin与Fair dispatch

    一:介绍 1.模型 有两种情形,分别是轮训分发与公平分发. 2.出现的场景 考虑到simple queue中的缺点. 因为生产者发送消息后,消费者消费要花费时间,这个会造成消息的堆积. 二:Round ...

  5. AeroSpike踩坑手记1:Architecture of a Real Time Operational DBMS论文导读

    又开了一个新的坑,笔者工作之后维护着一个 NoSQL 数据库.而笔者维护的数据库正是基于社区版本的 Aerospike打造而来.所以这个踩坑系列的文章属于工作总结型的内容,会将使用开发 Aerospi ...

  6. 未将对象引用设置到对象的实例 IIS

    CMD C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i

  7. 洛谷.4180.[模板]次小生成树Tree(Kruskal LCA 倍增)

    题目链接 构建完MST后,枚举非树边(u,v,w),在树上u->v的路径中找一条权值最大的边(权为maxn),替换掉它 这样在 w=maxn 时显然不能满足严格次小.但是这个w可以替换掉树上严格 ...

  8. 解决AD9中“......has no driver”的问题

  9. [CF1030E]Vasya and Good Sequences

    [CF1030E]Vasya and Good Sequences 题目大意: 给定一个长度为\(n(n\le3\times10^5)\)的数列\(a_i(1\le a_i\le10^{18})\). ...

  10. 转载: ASP.NET Core入门系列文章

    今天在网上发现了ithome上的asp.net core 系列文章,对于新手入门还不错,这里转载一下,也方便查阅. [Day01] 從頭開始 [Day02] 程式生命週期 (Application L ...