簡體   English   中英

Java給出了看似隨機的StackOverflowError

[英]Java gives seemingly random StackOverflowError

這是我的一個項目中的Factorial函數:

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(IntelliJ Community Edition 2018.2.1)中啟動調用fac(9000)的程序時,錯誤發生在3次中的2次,但是當我從shell啟動時它可能是調用超過300次或更多,同時只崩潰一次或兩次(我使用一個簡短的bash腳本來測試這個)。 我在Ubuntu 18.04上使用openjdk-11。

我已經嘗試使用-Xss1024k設置顯式堆棧大小(也使用了其他一些大小),但問題仍然存在。

我知道存儲在long中的結果將不再正確(當它沒有崩潰時結果為0 ),但我不知道是什么原因導致看似隨機的崩潰以及為什么它從內部運行時更頻繁地發生IDE。

這幾乎肯定是一個JIT問題。

值得注意的是,由於java中的“堆棧”不是非常傳統,因此StackOverflowError以非常奇怪的方式觸發。 我不會深入細節,但它似乎基本上是堆中的鏈表。

鑒於人們在評論中報告的一些症狀,我認為它與JIT密切相關。 引用西蒙的話

當我在一個簡單的for(int i = 0; i <100000; ++ i){fib(9000); 出現兩種情況之一:程序在第一次調用fib(9000)時崩潰,或者在100000次調用中都沒有崩潰。

通常,JIT應該在一致點激活,特別是如果運行中沒有變化。 如果您決定啟用編譯打印,您會注意到正在編譯的許多類似乎根本與您的程序無關。 這可能是有問題的,因為代碼可能需要一段時間才能編譯(JIT編譯作為隊列處理)。 另一個問題是JIT是非阻塞的。 這意味着JIT將被觸發,然后它將在另一個線程中編譯該方法,然后它將方法指針注入到程序中。 因為它是非阻塞的,所以它可能在函數觸發StackOverflowError后完成。

有很多方法可以提高成功幾率。

  1. 通過多次調用方法來預熱該方法,該數字具有從不觸發StackOverflowError的大小數字。 這應該給JIT時間注入已編譯的方法。
  2. 將程序設置為以單核模式運行。 這可以通過打開任務管理器,右鍵單擊Java進程,然后單擊Go to details來完成(我只能在Win10中確認如何執行此操作)。 這將帶您進入“詳細信息”頁面並選擇該流程。 您現在可以右鍵單擊流程詳細信息並選擇“ Set affinity ,取消選擇除一個之外的所有核心。 這應該強制程序在單個核心上下文中運行,這將導致所有線程都被阻塞,包括JIT。 這確實有一個值得注意的警告,GC現在將阻止。
  3. 按照與之前相同的步驟進入“詳細信息”頁面,右鍵單擊流程詳細信息並選擇“ Set priority ,然后選擇“ High (我發現選擇“ Realtime會導致系統停滯)。 這將把進程線程放在列表的頂部以便運行其他東西,這有望讓你的JIT在核心上獲得更多的連續時間來進行編譯。
  4. 把程序放到一個最小的linux服務器上,這將是#3之類的東西。 這里的目標是在服務器上運行其他任何東西。 這不是真的推薦,但可能有效。

對於#2和#3,您可能必須在程序中使用某些內容來停止執行,直到您設置優先級或關聯。 您可以選擇使用Process Hacker之類的程序來保存優先級或親和力,並在每次啟動時立即設置。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM