栈溢出 StackOverflowError

问题

生产环境栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
3282788         at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282789 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282790 Exception in thread "pool-250-thread-43" java.lang.StackOverflowError
3282791 at java.util.AbstractCollection.toArray(AbstractCollection.java:183)
3282792 at java.lang.String.split(String.java:2378)
3282793 at com.chedaia.task.address.util.ParkingPointUtil.isGps(ParkingPointUtil.java:32)
3282794 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:406)
3282795 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282796 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282797 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282798 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282799 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)

原因

递归调用次数太多导致
且启动脚本中设置了-Xss

1
-XX:PermSize=128m -Xss256k 

解决办法

由于涉及到的代码依赖较多,先不改动代码
加大线程栈大小到2m
1
-XX:PermSize=128m -Xss2m 


线程栈大小与递归次数测试

代码

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
recursion(0);
}

public static void recursion(int i) {
System.out.println(i);
i++;
recursion(i);
}

执行结果

结果:966

此时的栈设置为: -Xss128k

1
2
3
4
5
6
7
8
9
10
963
964
965
966
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
at sun.nio.cs.UTF_8.access$200(UTF_8.java:57)
at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636)
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java

不同栈大小下的递归次数

线程栈大小 递归次数约为
-Xss128k 966
-Xss256k 2467
-Xss512k 5447
-Xss1m 11404
-Xss2m 23323
-Xss3m 35247
-Xss4m 41306

方法中的局部变量会占用栈空间

当递归的方法中定义了一些局部变量时会占用线程栈的空间,从而影响到递归的次数
如将上面的方法稍微修改一下,增加3个局部变量:

1
2
3
4
5
6
7
8
public static void recursion(int i) {
System.out.println(i);
long a = 1l;
long b = 2l;
long c = a + b;
i++;
recursion(i);
}

在栈大小设置为: -Xss128k 时,结果是:623

1
2
3
4
5
6
7
8
9
620
621
622
623
java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
at sun.nio.cs.UTF_8.access$200(UTF_8.java:57)
at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636)
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)

官方说明

https://www.oracle.com/technetwork/java/hotspotfaq-138619.html#threads_oom

您可能遇到线程的默认堆栈大小的问题。在Java SE 6中,Sparc的默认值是32位VM中的512k, 64位VM中的1024k。

在x86 Solaris/Linux上,32位虚拟机中是320k, 64位虚拟机中是1024k。

在Windows上,默认的线程堆栈大小是从二进制文件(java.exe)读取的。在Java SE 6中,这个值在32位VM中是320k,在64位VM中是1024k。

您可以通过使用-Xss选项运行来减小堆栈大小。例如:

1
java -server -Xss64k

注意,在某些版本的Windows上,操作系统可能使用非常粗的粒度来计算线程堆栈大小。如果请求的大小小于默认大小1K或更大,则将堆栈大小四舍五入到默认大小;否则,堆栈大小将四舍五入为1 MB的倍数。

64k是每个线程允许的最小堆栈空间量。

不显式设置-Xss或-XX:ThreadStackSize时,或者把-Xss或者-XX:ThreadStackSize设为0,就是使用“系统默认值”。

用命令查看

1
2
3
4
5
6
7
# java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = 0 {pd product}
intx ThreadStackSize = 1024 {pd product}
intx VMThreadStackSize = 1024 {pd product}
java version "1.8.0_162"
Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)