文章目录

  • 问题信息
    • 出现问题的环境
    • 问题描述
    • 错误信息
  • 排查过程
    • 初次排查
    • 再起波澜
  • 问题原因
  • 解决方案
    • 修改JVM大小
    • SQL优化

问题信息

出现问题的环境

Linux + Tomcat + jdk1.8
Mybatis + 阿里的druid数据源

问题描述

通过手机号去数据库里面查询数据,手机号太多就会出现栈溢出异常。
linux 300+作业出现溢出
错误代码信息

错误信息

Caused by: java.lang.StackOverflowError
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.print(SQLASTOutputVisitor.java:182) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.sql.dialect.oracle.visitor.OracleParameterizedOutputVisitor.visit(OracleParameterizedOutputVisitor.java:79) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.sql.ast.expr.SQLCharExpr.accept0(SQLCharExpr.java:44) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:43) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.printAndAccept(SQLASTOutputVisitor.java:226) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:499) ~[druid-0.2.22.jar:0.2.22]

at com.alibaba.druid.filter.stat.StatFilter.mergeSql(StatFilter.java:145) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.filter.stat.StatFilter.createSqlStat(StatFilter.java:629) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.filter.stat.StatFilter.internalBeforeStatementExecute(StatFilter.java:397) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.filter.stat.StatFilter.statementExecuteBefore(StatFilter.java:345) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:438) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2927) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:118) ~[druid-0.2.22.jar:0.2.22]
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:493) ~[druid-0.2.22.jar:0.2.22]
at sun.reflect.GeneratedMethodAccessor76.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_73]
at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_73]
at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:55) ~[mybatis-3.2.1.jar:3.2.1]
at com.sun.proxy. P r o x y 282. e x e c u t e ( U n k n o w n S o u r c e ) [ ? : ? ] a t o r g . a p a c h e . i b a t i s . e x e c u t o r . s t a t e m e n t . P r e p a r e d S t a t e m e n t H a n d l e r . q u e r y ( P r e p a r e d S t a t e m e n t H a n d l e r . j a v a : 56 ) [ m y b a t i s − 3.2.1. j a r : 3.2.1 ] a t o r g . a p a c h e . i b a t i s . e x e c u t o r . s t a t e m e n t . R o u t i n g S t a t e m e n t H a n d l e r . q u e r y ( R o u t i n g S t a t e m e n t H a n d l e r . j a v a : 70 ) [ m y b a t i s − 3.2.1. j a r : 3.2.1 ] a t s u n . r e f l e c t . G e n e r a t e d M e t h o d A c c e s s o r 78. i n v o k e ( U n k n o w n S o u r c e ) [ ? : ? ] a t s u n . r e f l e c t . D e l e g a t i n g M e t h o d A c c e s s o r I m p l . i n v o k e ( D e l e g a t i n g M e t h o d A c c e s s o r I m p l . j a v a : 43 ) [ ? : 1.8. 0 7 3 ] a t j a v a . l a n g . r e f l e c t . M e t h o d . i n v o k e ( M e t h o d . j a v a : 497 ) [ ? : 1.8. 0 7 3 ] a t o r g . a p a c h e . i b a t i s . p l u g i n . P l u g i n . i n v o k e ( P l u g i n . j a v a : 59 ) [ m y b a t i s − 3.2.1. j a r : 3.2.1 ] a t c o m . s u n . p r o x y . Proxy282.execute(Unknown Source) ~[?:?] at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:56) ~[mybatis-3.2.1.jar:3.2.1] at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:70) ~[mybatis-3.2.1.jar:3.2.1] at sun.reflect.GeneratedMethodAccessor78.invoke(Unknown Source) ~[?:?] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_73] at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_73] at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59) ~[mybatis-3.2.1.jar:3.2.1] at com.sun.proxy. Proxy282.execute(UnknownSource) [?:?]atorg.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:56) [mybatis−3.2.1.jar:3.2.1]atorg.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:70) [mybatis−3.2.1.jar:3.2.1]atsun.reflect.GeneratedMethodAccessor78.invoke(UnknownSource) [?:?]atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [?:1.8.07​3]atjava.lang.reflect.Method.invoke(Method.java:497) [?:1.8.07​3]atorg.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59) [mybatis−3.2.1.jar:3.2.1]atcom.sun.proxy.Proxy280.query(Unknown Source) ~[?:?]
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:57) ~[mybatis-3.2.1.jar:3.2.1]
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:259) ~[mybatis-3.2.1.jar:3.2.1]
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:132) ~[mybatis-3.2.1.jar:3.2.1]
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:115) ~[mybatis-3.2.1.jar:3.2.1]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104) ~[mybatis-3.2.1.jar:3.2.1]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98) ~[mybatis-3.2.1.jar:3.2.1]
at sun.reflect.GeneratedMethodAccessor90.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_73]
at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_73]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:354) ~[mybatis-spring-1.2.0.jar:1.2.0]


排查过程

初次排查

由于是stackOverFlowError, 错误日志里面,有很长的日志都是druid在设置参数,怀疑是递归层级太深,超过了栈允许的最大深度。
想在自己的开发环境重现, Windows + Junit + jdk1.7,使用相同数据库,一次测试300+手机号,正常。 直接改成上限1000 (oracle in 传参上限),运行多次正常。
此时初步判断不是调用层级太深, 环境问题?
此时查看Linux上的JVM启动参数配置,-Xss256k (Thread Stack Size)

本地开发环境再验证时,增加-Xss256k设置,手机号一次验证数量到660左右时,出现相同的异常。 将Xss修改为512k,问题解决,Linux上问题也解决。
问题圆满解决?

补充点:
JVM栈出现StackOverFlowError的场景:栈调用层级太深,或栈申请不到内存

再起波澜

反馈给运维的同学, Xss需要设置为512k。 运维的同学不同意!!! 原该参数为128k,后来不满足需求调整为256k, 不同意再调大了。建议我们优化代码 ( 内存大小固定的情况下,单个线程占用的内存越大,总线程数量就会越少 )

what can I do ?

  • 提示日志级别, 出现问题时是debug,改成error ? 因为debug输出的日志多,会多定义一些局部变量 。 No,不行
  • 使用到Long类型的修改为Integer. 栈内存中,Long需要占用2个slot, Integer只需要占用一个。 No,不行

    没有想到好的头绪,先自己手工调整了服务器上的JVM参数. Xss修改为512k
  • 拖了几天,有次骑车突然闪现了一个很笨的办法 (当时自得意为灵光一闪)。 既然300+无法查询,内部实现的时候,每100个号查询一次,然后拼装结果?
    当时的想法是,300个号码能查询, 每次100个肯定可以查询,然后外层方法调用里面的具体实现,里层方法退出时栈 (栈帧)占用的空间释放掉,则应该不会超过256k的限制
    回来后进行验证, 不行, 为什么,
  • 后来查看druid连接池的具体实现,网上搜索了一些资料。需要先做SQL解析,生成语法树,然后根据目标数据库类型生成对应的SQL。 怀疑由于SQL太复杂导致解析复杂,方法调用太多,大量局部变量 ?
    对SQL进行修改,还原成最初的SQL (原来是由于有性能问题,每次in遍历的时候,所有手机号均需做一次加密操作), 该问题不再出现。

后来SQL改写了下,避免了SQL union的问题,但有个新的性能问题:对每个手机号需要做一次解密操作。 新问题尚未想到解决方案(文章后面有后来想到的其他思路)


问题原因

Thread Stack Size大小,问题环境设置为256k,内存申请不到,所以出现栈溢出的异常
阿里的druid框架,做SQL解析(visitor模式), 由于SQL很复杂, 很多SQL做union拼接, SQL解析时构造了很复杂的语法树



解决方案

修改JVM大小

-Xss参数由256k改到512k后,一次查询1000个手机号,不会出现栈溢出异常

##方法优化,降低占用的栈内存
没找到好的方案

SQL优化


同样带来了一个新问题, 每个手机号都需要做一次解密操作。
后来换了个角度想了下,把解密这个,可以先在程序里面做掉。或者反过来对外层数据加密也可以。

JVM StackOverFlowError相关推荐

  1. 写java线程导致电脑内存不足_如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码...

    程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...

  2. 【JVM】StackOverflowError与OutOfMemoryError

    · StackOverflowError与OutOfMemoryError是JVM中常见的有关内存的异常,需结合JVM来: · 在理解.区分这两个异常前,需要知道JVM中运行时数据区的结构:[见图1的 ...

  3. JVM如何处理StackOverflowError

    如何模拟StackOverflowError,可以参考: http://blog.csdn.net/u011983531/article/details/63250882 我们知道,当虚拟机栈容量太小 ...

  4. JVM——13.定位 StackOverflowError

    文章目录 1. Java虚拟机栈和方法调用 2. 什么情况会发生 StackOverflow 3. 模拟 StackOverflowError 4. StackOverflowError 的定位及解决 ...

  5. JVM中OutOFMemory和StackOverflowError异常代码

    1.Out of Memory 异常 右键Run As --->Run Configuration 设置JVM参数 -Xms20m -Xmx20m 上代码: 1 /** 2 * VM Args: ...

  6. JVM 常见异常及内存诊断

    栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存. 栈帧过多导致栈内存溢出 下述示例代码,由于递归 ...

  7. 面试高频!JVM必备教程~

    请你谈谈你对JVM的理解?Java8虚拟机和之间的变化更新? JVM类加载器是怎么样的?有几种? 什么是OOM,什么是StackOverFlowError? 怎么分析? JVM常用调优参数有哪写? G ...

  8. Tomcat 调优及 JVM 参数优化

    Tomcat 本身与 JVM 优化 Tomcat:调整Server.xml JVM:bat启动服务方式的话修改catalina.bat 服务式启动的话参考:http://www.cnblogs.com ...

  9. JVM学习--(一)基本原理

    前言 JVM一直是java知识里面进阶阶段的重要部分,如果希望在java领域研究的更深入,则JVM则是如论如何也避开不了的话题,本系列试图通过简洁易读的方式,讲解JVM必要的知识点. 运行流程 我们都 ...

最新文章

  1. android 分辨率合集,Android编程之分辨率处理相关代码段合集
  2. c语言的语言扩展的数据类型,C语言之数据类型
  3. (转)Git详解之一:Git起步
  4. 【IBatis.Net】 各种配置说明
  5. Azure手把手系列 4:深入了解Azure 一块钱当三块用
  6. java什么叫元素_什么是Java做什么 - 每个元素使它们成为对象类型?
  7. datagrid sortname如何定义多列_如何实现一个小说分页的功能
  8. ubuntu16.04安装opencv3.3
  9. 街机模拟器 WinKawaks 及街机 ROM 下载
  10. html表格宽度设置没效果,html表格宽度设置失效
  11. 普通台式机上Tesla M40显卡paddleGPU深度学习柯南的变身器上机体验
  12. 服务器域名 一级域名_HTTP简介:域名系统服务器
  13. C语言约分、求最大公约数?三角形用分数格式输出其较小锐角的正弦值
  14. 使用Java编写自己的区块链
  15. 空间相关性----地理探测器--学习记录
  16. 2022探索电商新模式 乐创播客共启未来
  17. 记录常用的代码工具(二)--Poi工具类
  18. 查看计算机网络ip,详细教你电脑ip地址怎么看
  19. mysql如何打码隐私数据列_MSSQL - 最佳实践 - 如何打码隐私数据列
  20. 关于完全卸载2345王牌输入法-win10

热门文章

  1. WPF x:Key标签
  2. VLookup函数怎么用?详细解析
  3. 2.2 单片机最小系统
  4. 免费分享:5本安卓开发经典书籍,Android 7编程入门经典(第4版),Android底层驱动分析和移植,底层驱动分析和移植
  5. Autofill Framework(自动填写)用法详解
  6. PID控制算法实践应用(一):PID算法的离散化
  7. 金立m5android经常自启,金立M5(全网通)一键救砖教程,轻松刷回官方系统
  8. linux主机路由命令,linux下路由设置详解
  9. 【2022】超详细的JAVA JDK配置和IDEA安装教程(Windows 版)
  10. concurrent.futures调研