栈内存溢出-StackOverflowError
java.lang.StackOverflowError-(线程)栈内存溢出错误
栈内存溢出是进行复杂运算时非常容易出现的错误。
下面test方法是一个死循环自己调自己的例子。运行这个方法,你会看到熟悉又不常见的java.lang.StackOverflowError:
1 package com.clz; 2 3 import org.junit.Test; 4 5 public class TestMain { 6 @Test public void testStackOverflowError() { 7 testStackOverflowError(); 8 } 9 10 }
运行结果:
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。
方法自己调自己,可能很少见,因为很容易发现问题。而类与类相互依赖(循环依赖)可能会出现的几率就会大一些了。尤其是诸如A->B->C->A这种链路长的,可能没那么容易发现。
如下示例中,A和B两个类出现了循环依赖,同样会抛出StackOverflowError。
// ----- class A package jstudy.ab; @Service public class A { @Autowired B b; public void testA1() { System.out.println("a.testA1()"); } public void testA2() { System.out.println("a.testA2()"); b.testB(); } // ----- class B package jstudy.ab; @Service public class B { @Autowired A a; public void testB(){ System.out.println("b.testB()"); a.testA2(); } }
执行如下testcase可以看到抛出了StackOverflowError。
package jstudy.ab; //import org.junit.jupiter.api.Test; import org.junit.Test; @SpringBootTest @RunWith(SpringRunner.class) public class ATest { @Autowired A a; @Test public void testA2() { a.testA2(); } }
执行结果及堆栈信息:
1 a.testA2() 2 b.testB() 3 a.testA2() 4 b.testB() 5 a.testA2() 6 b.testB() … 15038 java.lang.StackOverflowError 15039 at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77) 15040 at sun.nio.cs.UTF_8.access$200(UTF_8.java:57) 15041 at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636) 15042 at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691) 15043 at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579) 15044 at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271) 15045 at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125) 15046 at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207) 15047 at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129) 15048 at java.io.PrintStream.write(PrintStream.java:526) 15049 at java.io.PrintStream.print(PrintStream.java:669) 15050 at java.io.PrintStream.println(PrintStream.java:806) 15051 at jstudy.ab.A.testA2(A.java:16) 15052 at jstudy.ab.B.testB(B.java:12) 15053 at jstudy.ab.A.testA2(A.java:17) 15054 at jstudy.ab.B.testB(B.java:12) 15055 at jstudy.ab.A.testA2(A.java:17) … 16061 at jstudy.ab.A.testA2(A.java:17) 16062 at jstudy.ab.B.testB(B.java:12)
注意到以上运行结果,一共会打印出来1024条stacktrace信息(16062-15039+1=1024)。说明java中线程栈的长度是1024。
线程栈:thread stack。我们分析程序的jvm dump时,往往需要通过查看Thread Stack来分析问题。比如下图:
------------我是么么哒分割线----------------
我们来看如下代码的线程栈图示
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的线程栈内存,最终就会导致出现栈内存溢出——StackOverflowError。
而我在上周四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 } 。。。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/14832996.html