栈溢出的原因

在解决栈溢出问题之前,我们首先需要知道一般引起栈溢出的原因,主要有以下几点:

是否有递归调用,循环依赖调用

是否有大量循环或死循环

全局变量是否过多

局部变量过大,如:数组、List、Map数据过大

问题现象

我们一个很老的接口(近一年没有动过)在线上运行一段时间后报了StackOverflowError栈溢出,其他接口又能正常提供服务,错误日志:

java.lang.StackOverflowError

org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1303)

org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:977)

org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)

org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)

org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)

javax.servlet.http.HttpServlet.service(HttpServlet.java:648)

org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)

javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

com.wlqq.etc.deposit.web.filter.WebContextFilterDev.doFilter(WebContextFilterDev.java:78)

org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)

org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)

com.wlqq.library.httpcommons.sso.filter.SSOSessionFilter.doFilter(SSOSessionFilter.java:95)

org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)

org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)

org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

io.opentracing.contrib.web.servlet.filter.TracingFilter.doFilter(TracingFilter.java:187)

root cause java.lang.StackOverflowError

java.util.Collections$UnmodifiableCollection.get(Collections.java:1030)

java.util.Collections$UnmodifiableCollection.get(Collections.java:1030)

java.util.Collections$UnmodifiableCollection.get(Collections.java:1030)

java.util.Collections$UnmodifiableCollection.get(Collections.java:1030)

java.util.Collections$UnmodifiableCollection.get(Collections.java:1030)

java.util.Collections$UnmodifiableCollection.get(Collections.java:1030)

...

解决过程

review代码

首先按照上面栈溢出的原因,我对该接口的业务代码review一遍,但是很遗憾,没有发现有任何的问题,没有死循环,循环依赖,大的局部变量等。

从日志上看错误日志是在DispatcherServlet中出现的,大致看了下DispatcherServlet也没看出啥问题,因为如果是框架的问题,那么也不应该就出现在这一个接口上。于是针对这个接口我也看了下他的查询语句,对应Mybatis的resultMap等。都没啥问题,就是一个简单的查询语句,resultMap也没有嵌套,返回实体也没有嵌套类,在正常不过了。

本地重现

因为这是线上环境,我们排查问题十分受限,而且是栈溢出,自己也确实不知道用啥命令和工具可以借助,于是我在本地将代码跑起来,用JMeter工具对该接口进行压测,果然,本地也出现了相同问题,能在本地重现我就松了一口气了,因为真相离我们已经很近了。

使用断点

我在DispatcherServlet报错位置打上了断点,结果debug栈出来后,我还是一无所获,因为栈信息就和上线爆出的信息一模一样。这个信息连问题具体是在DispatcherServlet代码中哪一行报出的都没法定位到。

然后我将断点移动到Collections的1309行。通过不断的尝试我看到了这个debug栈:

...

get:1309, Collections$UnmodifiableList (java.util) [7]

get:1309, Collections$UnmodifiableList (java.util) [6]

get:1309, Collections$UnmodifiableList (java.util) [5]

get:1309, Collections$UnmodifiableList (java.util) [4]

get:1309, Collections$UnmodifiableList (java.util) [3]

get:1309, Collections$UnmodifiableList (java.util) [2]

get:1309, Collections$UnmodifiableList (java.util) [1]

handleResultSets:159, DefaultResultSetHandler (org.apache.ibatis.executor.resultset)

query:63, PreparedStatementHandler (org.apache.ibatis.executor.statement)

query:78, RoutingStatementHandler (org.apache.ibatis.executor.statement)

doQuery:62, SimpleExecutor (org.apache.ibatis.executor)

queryFromDatabase:303, BaseExecutor (org.apache.ibatis.executor)

query:154, BaseExecutor (org.apache.ibatis.executor)

query:102, CachingExecutor (org.apache.ibatis.executor)

query:82, CachingExecutor (org.apache.ibatis.executor)

invoke:-1, GeneratedMethodAccessor98 (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

proceed:49, Invocation (org.apache.ibatis.plugin)

intercept:85, PageInterceptor (com.wlqq.etc.deposit.common.interceptor)

invoke:61, Plugin (org.apache.ibatis.plugin)

query:-1, $Proxy66 (com.sun.proxy)

selectList:120, DefaultSqlSession (org.apache.ibatis.session.defaults)

selectList:113, DefaultSqlSession (org.apache.ibatis.session.defaults)

invoke:-1, GeneratedMethodAccessor100 (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

invoke:386, SqlSessionTemplate$SqlSessionInterceptor (org.mybatis.spring)

selectList:-1, $Proxy49 (com.sun.proxy)

selectList:205, SqlSessionTemplate (org.mybatis.spring)

executeForMany:122, MapperMethod (org.apache.ibatis.binding)

execute:64, MapperMethod (org.apache.ibatis.binding)

invoke:53, MapperProxy (org.apache.ibatis.binding)

queryOpenCardOrders:-1, $Proxy132 (com.sun.proxy)

queryOpenCardOrders:1506, OpenCardServiceImpl (com.wlqq.etc.deposit.service.impl)

invoke:-1, GeneratedMethodAccessor113 (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

invokeJoinpointUsingReflection:302, AopUtils (org.springframework.aop.support)

invoke:201, JdkDynamicAopProxy (org.springframework.aop.framework)

queryOpenCardOrders:-1, $Proxy154 (com.sun.proxy)

queryOpenCardOrders:90, OpenCardOrderController (com.wlqq.etc.deposit.web.controller)

invoke:-1, OpenCardOrderController$$FastClassBySpringCGLIB$$1a780c6e (com.wlqq.etc.deposit.web.controller)

invoke:204, MethodProxy (org.springframework.cglib.proxy)

invokeJoinpoint:717, CglibAopProxy$CglibMethodInvocation (org.springframework.aop.framework)

proceed:157, ReflectiveMethodInvocation (org.springframework.aop.framework)

proceed:85, MethodInvocationProceedingJoinPoint (org.springframework.aop.aspectj)

doAround:62, RequestLogAOP (com.wlqq.etc.deposit.web.filter)

invoke:-1, GeneratedMethodAccessor112 (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

invokeAdviceMethodWithGivenArgs:621, AbstractAspectJAdvice (org.springframework.aop.aspectj)

invokeAdviceMethod:610, AbstractAspectJAdvice (org.springframework.aop.aspectj)

invoke:68, AspectJAroundAdvice (org.springframework.aop.aspectj)

proceed:179, ReflectiveMethodInvocation (org.springframework.aop.framework)

proceed:85, MethodInvocationProceedingJoinPoint (org.springframework.aop.aspectj)

doAround:67, ValidateArgsAOP (com.wlqq.library.validate.aop)

invoke:-1, GeneratedMethodAccessor111 (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

invokeAdviceMethodWithGivenArgs:621, AbstractAspectJAdvice (org.springframework.aop.aspectj)

invokeAdviceMethod:610, AbstractAspectJAdvice (org.springframework.aop.aspectj)

invoke:68, AspectJAroundAdvice (org.springframework.aop.aspectj)

proceed:179, ReflectiveMethodInvocation (org.springframework.aop.framework)

invoke:92, ExposeInvocationInterceptor (org.springframework.aop.interceptor)

proceed:179, ReflectiveMethodInvocation (org.springframework.aop.framework)

intercept:653, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)

queryOpenCardOrders:-1, OpenCardOrderController$$EnhancerBySpringCGLIB$$6931dba9 (com.wlqq.etc.deposit.web.controller)

invoke:-1, GeneratedMethodAccessor110 (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

doInvoke:221, InvocableHandlerMethod (org.springframework.web.method.support)

invokeForRequest:137, InvocableHandlerMethod (org.springframework.web.method.support)

invokeAndHandle:110, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)

invokeHandlerMethod:806, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)

handleInternal:729, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)

handle:85, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)

doDispatch:959, DispatcherServlet (org.springframework.web.servlet)

doService:893, DispatcherServlet (org.springframework.web.servlet)

processRequest:970, FrameworkServlet (org.springframework.web.servlet)

doPost:872, FrameworkServlet (org.springframework.web.servlet)

service:648, HttpServlet (javax.servlet.http)

service:846, FrameworkServlet (org.springframework.web.servlet)

service:729, HttpServlet (javax.servlet.http)

internalDoFilter:292, ApplicationFilterChain (org.apache.catalina.core)

doFilter:207, ApplicationFilterChain (org.apache.catalina.core)

doFilter:52, WsFilter (org.apache.tomcat.websocket.server)

internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)

doFilter:207, ApplicationFilterChain (org.apache.catalina.core)

doFilter:78, WebContextFilterDev (com.wlqq.etc.deposit.web.filter)

invokeDelegate:346, DelegatingFilterProxy (org.springframework.web.filter)

doFilter:262, DelegatingFilterProxy (org.springframework.web.filter)

internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)

doFilter:207, ApplicationFilterChain (org.apache.catalina.core)

doFilter:95, SSOSessionFilter (com.wlqq.library.httpcommons.sso.filter)

invokeDelegate:346, DelegatingFilterProxy (org.springframework.web.filter)

doFilter:262, DelegatingFilterProxy (org.springframework.web.filter)

internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)

doFilter:207, ApplicationFilterChain (org.apache.catalina.core)

doFilterInternal:85, CharacterEncodingFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)

doFilter:207, ApplicationFilterChain (org.apache.catalina.core)

doFilter:187, TracingFilter (io.opentracing.contrib.web.servlet.filter)

internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)

doFilter:207, ApplicationFilterChain (org.apache.catalina.core)

invoke:212, StandardWrapperValve (org.apache.catalina.core)

invoke:106, StandardContextValve (org.apache.catalina.core)

invoke:502, AuthenticatorBase (org.apache.catalina.authenticator)

invoke:141, StandardHostValve (org.apache.catalina.core)

invoke:79, ErrorReportValve (org.apache.catalina.valves)

invoke:616, AbstractAccessLogValve (org.apache.catalina.valves)

invoke:88, StandardEngineValve (org.apache.catalina.core)

service:528, CoyoteAdapter (org.apache.catalina.connector)

process:1099, AbstractHttp11Processor (org.apache.coyote.http11)

process:670, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)

doRun:2508, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)

run:2497, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)

runWorker:1142, ThreadPoolExecutor (java.util.concurrent)

run:617, ThreadPoolExecutor$Worker (java.util.concurrent)

run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)

run:745, Thread (java.lang)

看到这个deug栈和我们的报错就很类似,于是我又看了一下List对象,直接提示了栈溢出:

通过上图我确认我找对了位置,然后根据debug栈,找这个list的源头。

我发现这个list就是mybatis的resultMaps,在DefaultResultSetHandler#handleResultSets方法中的resultMaps也报了栈溢出,resultMaps又来自mappedStatement,于是我们只要找到mappedStatement源头就行了。

在DefaultSqlSession#selectListObject, RowBounds)方法中我找到了MappedStatement的源头,它是直接从Mybatis的configuration对象中获取的一个缓存对象。

@Override

public List selectList(String statement, Object parameter, RowBounds rowBounds) {

try {

MappedStatement ms = configuration.getMappedStatement(statement);

return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);

} finally {

ErrorContext.instance().reset();

}

}

通过断点信息我发现ms对象的resultMaps属性是正常的。而且我惊奇的发现这个ms对象和我们后面报错的mappedStatement对象不是同一个对象,于是我猜测后面又代码将这个mappedStatement给改了。然后我通过查看debug栈我发现,在分页插件中,为了实现分页它会将mappedStatement对象给改了。

问题根源

从上面我们定位到了是分页插件中getPageStatement()方法,将Mybatis的mappedStatement给改了,下面是源码我们看下是如何修改的:

@Intercepts({@Signature(

type = Executor.class,

method = "query",

args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})

public class PageInterceptor implements Interceptor {

private static final int MAPPED_STATEMENT_INDEX = 0;

private static final int PARAMETER_INDEX = 1;

private static final int ROWBOUNDS_INDEX = 2;

private static final String sql = "sql", SQLSOURCE_STRING = "sqlSource";

private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();

private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

private static final Map BUILDER_MAP = new HashMap();

//处理SQL

public static final SqlParser sqlParser = new SqlParser();

@SuppressWarnings({"unchecked", "rawtypes"})

@Override

public Object intercept(final Invocation invocation) throws Throwable {

final Object[] queryArgs = invocation.getArgs();

final MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];

final BoundSql boundSql = ms.getBoundSql(queryArgs[PARAMETER_INDEX]);

final Object paramObj = boundSql.getParameterObject();

Page> page = null;

if (paramObj instanceof MapperMethod.ParamMap) { //如果为多参数

for (Object value : ((MapperMethod.ParamMap) paramObj).values()) {

if (value instanceof Page) {

page = (Page>) value;

break;

}

}

}

if (paramObj instanceof Page) { //如果参数为单个page对象

page = (Page>) paramObj;

}

if (page != null) {

int count = getCount(((Executor) invocation.getTarget()).getTransaction().getConnection(), boundSql, paramObj, ms);

page.setTc(count);

if (count != 0) {

queryArgs[ROWBOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);

queryArgs[MAPPED_STATEMENT_INDEX] = getPageStatement(ms, boundSql, page);

page.setDatas((List) invocation.proceed());

}

return page.getDatas();

}

return invocation.proceed();

}

private static final MappedStatement getPageStatement(MappedStatement ms, BoundSql boundSql, Page> page) {

String id = ms.getId();

Builder builder = BUILDER_MAP.get(id);

if (builder == null) {

builder = new Builder(ms.getConfiguration(), ms.getId(), new ExtSqlSource(boundSql), ms.getSqlCommandType());

builder.resource(ms.getResource());

builder.fetchSize(ms.getFetchSize());

builder.statementType(ms.getStatementType());

builder.keyGenerator(ms.getKeyGenerator());

if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {

StringBuffer keyProperties = new StringBuffer();

for (String keyProperty : ms.getKeyProperties()) {

keyProperties.append(keyProperty).append(",");

}

keyProperties.delete(keyProperties.length() - 1, keyProperties.length());

builder.keyProperty(keyProperties.toString());

}

builder.timeout(ms.getTimeout());

builder.parameterMap(ms.getParameterMap());

builder.resultMaps(ms.getResultMaps());

builder.resultSetType(ms.getResultSetType());

builder.cache(ms.getCache());

builder.flushCacheRequired(ms.isFlushCacheRequired());

builder.useCache(ms.isUseCache());

BUILDER_MAP.put(id, builder);

}

ms = builder.build();

MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY)

.setValue(sql, getPageSql(boundSql.getSql(), page));

MetaObject.forObject(ms, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY)

.setValue(SQLSOURCE_STRING, new ExtSqlSource(boundSql));

return ms;

}

private static final int getCount(Connection connection, BoundSql boundSql, Object paramObj,

MappedStatement mappedStatement) {

int count = 0;

ResultSet rs = null;

PreparedStatement countStmt = null;

try {

final String countSql = getCountSql(boundSql.getSql());

countStmt = connection.prepareStatement(countSql);

final DefaultParameterHandler handler = new DefaultParameterHandler(mappedStatement, paramObj, boundSql);

handler.setParameters(countStmt);

rs = countStmt.executeQuery();

if (rs.next()) {

count = rs.getInt(1);

}

} catch (SQLException e) {

throw new SystemException("SQL invalid", e);

} finally {

try {

if (rs != null) {

rs.close();

}

if (countStmt != null) {

countStmt.close();

}

} catch (SQLException e) {

//throw new SystemException("SQL invalid", e);

e.printStackTrace();

}

}

return count;

}

private static String getCountSql(String originalSql) { //count sql

return sqlParser.getSmartCountSql(originalSql);

}

private static String getPageSql(String originalSql, Page> page) {

return originalSql + " limit " + page.getStart() + "," + page.getPs();

}

@Override

public Object plugin(Object target) {

return Plugin.wrap(target, this);

}

@Override

public void setProperties(Properties props) {

}

private static class ExtSqlSource implements SqlSource {

BoundSql boundSql;

protected ExtSqlSource(BoundSql boundSql) {

this.boundSql = boundSql;

}

@Override

public BoundSql getBoundSql(Object parameterObject) {

return boundSql;

}

}

}

我们可以看到这个Mybatis分页插件的实现原理是,通过每次修改MappedStatement对象中的SQL语句来实现的分页。这段代码缓存了MappedStatement.Builder对象,通MappedStatement.Builder#build()对象来构建MappedStatement对象。在这里就出现了第一个错误点,它直接使用的是HashMap来缓存对象,HashMap是线程不安全的,如果是jdk1.7以前,HashMap在扩容的时候会发生循环调用,进而导致栈溢出,这里应该使用ConcurrentHashMap来做缓存。但是我们的问题不是HashMap引起的,因为我们用的是JDK1.8,并且在我压测过程中并没有发生扩容。

于是我有看了一下MappedStatement.Builder#build()方法源码,代码如下:

public MappedStatement build() {

assert mappedStatement.configuration != null;

assert mappedStatement.id != null;

assert mappedStatement.sqlSource != null;

assert mappedStatement.lang != null;

mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);

return mappedStatement;

}

通过这段代码我发现,前面全是判断,最后就是对resultMaps做了一下装饰,Collections.unmodifiableList的主要作用就是将我们的list变成一个不可以修改的list,源码如下:

public static List unmodifiableList(List extends T> list) {

return (list instanceof RandomAccess ?

new Collections.UnmodifiableRandomAccessList<>(list) :

new Collections.UnmodifiableList<>(list));

}

看到这段代码我刚以为找到了根源,但是看下源码,就失望了,这段代码太正常不过,就是对原来的list装饰了一下,然后将一些修改方法给屏蔽了。

于是我又倒回去看了下MappedStatement.Builder源码,发现了一个关键点,我们的MappedStatement的构建是使用的建造者模式,每个Builder````对象会去建造一个MappedStatement```,源码如下:

public static class Builder {

private MappedStatement mappedStatement = new MappedStatement();

public MappedStatement build() {

assert mappedStatement.configuration != null;

assert mappedStatement.id != null;

assert mappedStatement.sqlSource != null;

assert mappedStatement.lang != null;

mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);

return mappedStatement;

}

}

通过这段代码我发现,每次调用MappedStatement.Builder#build()方法返回的同一个mappedStatement对象,并不是我们我们想的那样,每次build()方法会返回不同的对象。这就引出的这个插件的第二个错误点,在PageInterceptor#getPageStatement()方法中有如下代码:

private static final MappedStatement getPageStatement(MappedStatement ms, BoundSql boundSql, Page> page) {

String id = ms.getId();

Builder builder = BUILDER_MAP.get(id);

if (builder == null) {... }

ms = builder.build();

MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY)

.setValue(sql, getPageSql(boundSql.getSql(), page));

MetaObject.forObject(ms, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY)

.setValue(SQLSOURCE_STRING, new ExtSqlSource(boundSql));

return ms;

}

ms每次是同一个对象,在后续我们为了实现分页将该对象的sql给改了,在并发情况下,因为同时修改了同一个共享变量,会导致后续分页会时出现数据错乱的现象。但是这个错误和我们这次需要定位的问题没太大关系。

但是通过分析我确定问题一定是出现在了这行代码身上

ms = builder.build();

于是我又倒回去看了build()源码:

public MappedStatement build() {

...

mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);

return mappedStatement;

}

有效代码就只有一行,通过上面分析我们发现,每次build的时候mappedStatement是同一个对象,那么每次build()的时候,这段代码就会将自己给装饰一次,源码如下:

mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);

如果请求量大,这行代码就是在对自己不停的装饰,效果如下:

Collections.unmodifiableList(

Collections.unmodifiableList(

Collections.unmodifiableList(

Collections.unmodifiableList(

Collections.unmodifiableList(

Collections.unmodifiableList(

Collections.unmodifiableList(

...)))))));

当层级达到一定数量后,我们再调用这个list的get方法时就会发生调用链太长,进而将方法栈撑爆,出现栈溢出。到这里就找到了问题的根源。

解决方案

问题根源就是我们缓存了MappedStatement.Builder对象,我们去掉缓存后,代码恢复了正常。

我们不去新创建MappedStatement,直接修改原有MappedStatement的sql语句,在原来sql语句后面加上limit ?,?,最后分页信息通过参数传入。

倒推问题答案

为什么这个问题以前运行得好好的,直到几年后的今天才被发现? 这是因为我们以前这个服务发版很频繁,导致每次发版后这个装饰的层级被清空了。

为什么只有这一个接口出现了问题? 这是因为这个接口是使用分页查询接口中访问量最大的那个接口,所以它最先出现问题。

为什么线上只有那么一两台机器出现问题? 这是因为出问题的机器负载高一些,到时这些机器先出现问题。

总结

栈溢出的原因基本上就是我上面列举的那些,但是我们在编写程序的过程中都会有意识的避开这些问题,所以线上出现栈溢出的可能性很小,但是一旦出现就不好排查。我们需要静下心来慢慢分析,总会找到问题根源的,只是过程有点痛苦。

在没有完全了解Mybatis运行原理的情况下,不建议做Mybatis的插件开发。

没动过代码不代表系统就不会出现问题。

java 栈溢出异常_一次栈溢出问题的排查 StackOverflowError相关推荐

  1. java解决异常_聊聊Java中的异常及处理

    在编程中异常报错是不可避免的.特别是在学习某个语言初期,看到异常报错就抓耳挠腮,常常开玩笑说编程1分钟,改bug1小时.今天就让我们来看看什么是异常和怎么合理的处理异常吧! 异常与error介绍 下面 ...

  2. java runnable 异常_详解Java中多线程异常捕获Runnable的实现

    详解Java中多线程异常捕获Runnable的实现 1.背景: Java 多线程异常不向主线程抛,自己处理,外部捕获不了异常.所以要实现主线程对子线程异常的捕获. 2.工具: 实现Runnable接口 ...

  3. java类型转换异常_解决java.lang.ClassCastException的java类型转换异常的问题

    解决java.lang.ClassCastException的java类型转换异常的问题,异常,对象,错误,给大家,会报 解决java.lang.ClassCastException的java类型转换 ...

  4. java如何解决栈溢出问题_如何解决栈溢出

    1,什么是栈溢出? 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出. 2,解决方案: 方法一:用栈把递归转换成非递归 通常,一个函数在调 ...

  5. java如何解决栈溢出问题_怎样解决栈溢出

    1,什么是栈溢出? 由于栈一般默觉得1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出. 2,解决方式: 方法一:用栈把递归转换成非递归 通常,一个函数在调 ...

  6. sas java 虚拟机异常_深入理解JAVA虚拟机之异常诊断

    常见的JAVA虚拟机HotSpot虚拟机运行时数据库由5部分构成:方法区,堆,虚拟机栈,本地方法栈,程序计数器.下面列举各个部分可能出现的异常及其出现原因. 1.方法区存放的已被虚拟机加载的类型信息, ...

  7. 简述java中异常_柴涛666 的日志-简述java中处理异常的两种方式。

    抓try 抛throwspackage 异常; import java.util.Scanner; public class a { public static void main(String[] ...

  8. java 实例化异常_如何处理实例化类对象时发生的异常

    java version "1.7.0_45" 你好 我正在构造函数中初始化类方法.但是,新URL(uploadUrl)将在构造函数中引发异常.因此,如果发生这种情况,用户应该无法 ...

  9. ikvm Java中异常_使用IKVMC将Java转换为.NET库 – 警告IKVMC0108:...

    有Java工具(叫做Mallet) http://mallet.cs.umass.edu/download.php 我想在我的.NET项目中使用它. 为了首先将此工具转换为.NET库,我尝试使用Apa ...

最新文章

  1. MATLAB编写ode文件,MATLABODE45问题M文件为br/functiondq 爱问知识人
  2. html里注释的写法正确的是,网页怎么注释语句 HTML的注释正确写法是?
  3. ffmpeg 将拆分的数据合成一帧_FFmpeg + OpenGLES 实现视频解码播放和视频滤镜
  4. 求H21时的仿射变换要参考当前坐标系
  5. 计算机二级考试c语言冲刺,计算机二级C语言考试冲刺练习题
  6. 爬虫入门之绘图matplotlib与词云(七)
  7. FFmpeg学习(5)——视频加水印
  8. 使用Go构建区块链 第3部分:持久化和cli
  9. 小米笔记本linux无线网卡驱动,小米笔记本在Kali Linux下所遇问题部分解决方案
  10. html表格变的能够输入法,Excel单元格怎么自动切换输入法 如何Excel中输入法的快速切换...
  11. 初级电工技术实训考核装置
  12. python 除法符号_python的除法运算符是什么
  13. 科学计算机怎么算别人生日,秒算任意一天是星期几的人是怎么做到的?方法居然这么简单...
  14. 【失业的程序员】选修计算机专业的伤与痛.....
  15. 史玉柱自述:我的十大管理心得
  16. LOJ 6198. 谢特(后缀数组+可持久化Trie)
  17. UR机器人TCP通讯示例 详细例程,手把手教会你
  18. Python Socket 网络通信详解
  19. java转义字符之换行字符
  20. 热烈祝贺:广东省快递行业协会加入2023上海国际快递物流展

热门文章

  1. 人工智能的历史、现在与未来
  2. HAWQ上安装PXF插件,并访问HDFS文件数据
  3. 首尔日记——青年旅舍
  4. 武汉女孩勇揭电视购物黑幕
  5. TightVNC Windows下远程连接Linux桌面
  6. 计算机专业技术人员最高什么级,事业单位技术岗职称(事业单位专业技术岗(计算机专业)考职称)...
  7. 操作系统课堂同步练习选择题(第二章)题库信阳师范学院柳春华老师
  8. 《世界棒球》:19 世纪棒球规则的演变
  9. FastDFS中long2buff解析笔记
  10. iMobile与Online在线场景数据的交互