这是我的一个项目内部的阶乘函数:
public class Fac {
public static void main(String[] args) {
System.out.println(fac(9000));
}
public static long fac(long x) {
if (x <= 1) {
return 1;
}
else {
return x * fac(x-1);
}
}
}
当此函数以较大的参数运行时,例如fac(9000)
,有时但并非总是会导致StackOverflowError。我注意到,当我从IDE内部启动调用fac(9000)
的程序(IntelliJ Community Edition 2018.2.1)时,错误发生的次数大约是三分之二,但是当我从外壳启动时,可以被调用超过300次或更多,而崩溃仅一次或两次(我使用了简短的bash脚本对此进行了测试)。我在Ubuntu 18.04上使用openjdk-11。
我尝试使用-Xss1024k
设置显式堆栈大小(也使用了其他一些大小),但是问题仍然存在。
我知道存储在long中的结果将不再正确(当它不崩溃时,结果为0
),但是我不知道是什么原因导致了看似随机的崩溃以及为什么从IDE内部运行时,发生这种情况的频率更高。
答案 0 :(得分:0)
几乎可以肯定这是一个准时制问题。
值得注意的是,由于Java中的“堆栈”不是很常规,因此StackOverflowError
的触发方式很奇怪。我不会太深入细节,但是它本质上似乎是堆中的链表。
鉴于人们在评论中报告了一些症状,我认为这与JIT密切相关。引用西蒙
当我以简单的for(int i = 0; i <100000; ++ i)调用该方法时{fib(9000); }出现以下两种情况之一:程序在第一次调用fib(9000)时崩溃,或者在100000次调用中都没有崩溃。
通常,应该在一致的点激活JIT,尤其是在运行中没有差异的情况下。如果您决定打开编译打印,则会注意到正在编译的许多类似乎根本与您的程序无关。从某种意义上来说,这可能是有问题的,因为您的代码可能需要一段时间才能被编译(JIT编译作为队列处理)。另一个问题是JIT是非阻塞的。这意味着将触发JIT,然后将其编译到另一个线程中,然后将方法指针注入程序中。因为它是非阻塞的,所以它可能在函数触发StackOverflowError
之后结束。
有很多方法可以提高成功几率。
Go to details
,可以完成此操作(我只能在Win10中确认如何执行此操作)。这将带您选择过程的“详细信息”页面。现在,您可以右键单击流程详细信息,然后选择Set affinity
,取消选择除一个之外的所有内核。这将强制程序在单个核心上下文中运行,这将导致所有线程(包括JIT)被阻塞。确实有一个值得注意的警告,即GC现在将被阻塞。Set priority
,然后选择High
(我发现选择Realtime
可能会停滞系统)。这样一来,进程线程将在列表中排在首位,以运行其他事情,这有望使您的JIT在内核上获得更多连续时间。对于#2和#3,在设置优先级或相似性之前,您可能必须在程序中添加一些内容以停止执行。您可以选择使用Process Hacker之类的程序来保存优先级或相似性,并在每次启动时立即进行设置。