栈内存溢出-StackOverflowError
Posted buguge
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈内存溢出-StackOverflowError相关的知识,希望对你有一定的参考价值。
下面test方法是一个死循环自己调自己的例子。运行这个方法,你会看到熟悉又不常见的java.lang.StackOverflowError(栈内存溢出错误):
1 package com.clz; 2 3 import org.junit.Test; 4 5 public class TestMain { 6 @Test 7 public void testStackOverflowError() { 8 testStackOverflowError(); 9 } 10 11 }
运行结果:
java.lang.StackOverflowError at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1条stackTrace) at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第2条stackTrace) at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第3条stackTrace) 。。。。。。 at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第500条stackTrace) 。。。。。。 at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1022条stackTrace) at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1023条stackTrace) at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1024条stackTrace)
注意到以上运行结果,一共会打印出来1024条stacktrace信息。说明java中线程栈的长度是1024。
------------我是么么哒分割线----------------
java.lang.StackOverflowError栈内存溢出是进行复杂运算时非常容易出现的错误。
我们来看如下代码的线程栈图示
public class Demo { public static void main(String[] args) { String message="hello JVisualVM world"; hello(message); } private static void hello(String text) { System.out.println(text); } }
在java中,虚拟机会为每个任务的处理分配一个线程, 在这个线程里,每次调用一个方法,都会将本次方法调用的栈桢压入虚拟机栈里,这个栈桢里保存着方法内部的局部变量和其他信息。 不过呢,每个线程的虚拟机栈的大小是固定的,默认为1MB(上面的1024)。
既然一个线程的虚拟机栈内存大小是有限的,那么假设不停的调用各种方法,对应的栈桢不停的压入栈中。当这些大量的栈桢消耗完毕这个1MB的线程栈内存,最终就会导致出现栈内存溢出的情况。
------------我是么么哒分割线----------------
而我在上周四boss开工改版时,对redis分布式锁接口方法做了调整,却因为一个失误导致了StackOverflowError。
>>先看接口定义,然后再说问题:
1 package com.emax.zhenghe.common.concurrent.distributeRedisLock; 2 3 public interface DistributedLock { 4 5 boolean lock(String key); 6 7 boolean lock(String key, int retryTimes); 8 9 boolean lock(String key, int retryTimes, long sleepMillis); 10 11 boolean lock(String key, long expireMillis); 12 13 boolean lock(String key, long expireMillis, int retryTimes); 14 15 boolean lock(String key, long expireMillis, int retryTimes, long sleepMillis); 16 17 boolean releaseLock(String key); 18 }
>>接下来说问题:
分布式锁在技术层面有两种应用场景:
1. 可以保证幂等性(防重与幂等有区别:幂等通常是对并发请求的防重控制;防重除了需要分布式保证幂等以外,还需要做数据防重校验,因为重复请求可能不是并发请求过来的,有可能是隔了很长时间的重复数据提交,就是用DCL)
2. 实现进程同步(类似于线程synchronized锁):当锁存在时,需要不断尝试重试取锁,实现自旋等待。
这个接口正好也为两种应用场景定义了方法API。问题在于,我们看这些lock重载方法,比较第7行的lock(String,int)与第11行的lock(String,long),再比较第9行的lock(String,int,long)与第13行的lock(String,long,int),太容易误用了。事实也证明了这一点,项目的业务代码里有很多用来保证幂等性的逻辑调用的是lock(String,int)或lock(String,long,int),而非lock(String,long),那么,显然无法达到幂等控制的效果。
为了解决项目中现存的这种误用,并规避日后的误用,有必要重构这个接口。如下是第一版:
1 package com.emax.zhenghe.common.concurrent.distributeRedisLock; 2 3 public interface DistributedLock { 4 boolean lock(String key); 5 6 // boolean lock(String key, int retryTimes); 7 8 // boolean lock(String key, int retryTimes, long sleepMillis); 9 // boolean synchronize(String key, int retryTimes, long sleepMillis); 10 11 boolean lock(String key, long expireMillis); 12 13 /** 14 * 注:有很多项目很多代码调用了这个方法,过渡阶段先保留这个方法api 15 * 注:此方法不再使用。请不要使用过期的方法 16 * @param key 17 * @param expireMillis 18 * @param useless 有很多地方在用,不得不定义这个寂寞参数 19 * @return 20 */ 21 @Deprecated 22 default boolean lock(String key, long expireMillis,int useless){ 23 return lock(key, expireMillis, 0); 24 } 25 26 // boolean lock(String key, long expireMillis, int retryTimes); 27 boolean synchronize(String key, long expireMillis, int retryTimes); 28 29 // boolean lock(String key, long expireMillis, int retryTimes, long sleepMillis); 30 boolean synchronize(String key, long expireMillis, int retryTimes, long sleepMillis); 31 32 boolean releaseLock(String key); 33 34 boolean releaseLockUnsafe(String key); 35 }
导致出现StackOverflowError的,正是第22行标记了过时的lock(String key, long expireMillis,int useless)。这个方法调用的是其自身!而我的本意是要它调用第11行的lock(key, expireMillis):
。。。 13 /** 14 * 注:有很多项目很多代码调用了这个方法,过渡阶段先保留这个方法api 15 * 注:此方法不再使用。请不要使用过期的方法 16 * @param key 17 * @param expireMillis 18 * @param useless 有很多地方在用,不得不定义这个寂寞参数 19 * @return 20 */ 21 @Deprecated 22 default boolean lock(String key, long expireMillis,int useless){ 23 return lock(key, expireMillis); 24 } 。。。
以上是关于栈内存溢出-StackOverflowError的主要内容,如果未能解决你的问题,请参考以下文章
JPA出现recursion 死循环导致栈内存溢出问题 Could not write JSON: Infinite recursion (StackOverflowError)
五种内存溢出案例总结:涵盖栈深度溢出永久代内存溢出本地方法栈溢出JVM栈内存溢出和堆溢出