亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么我可以達到不確定的最大遞歸深度?

為什么我可以達到不確定的最大遞歸深度?

嗶嗶one 2019-11-04 09:25:08
我決定嘗試一些實驗,以了解關于堆棧幀的大小以及當前執行的代碼在堆棧中的距離的發現。我們可以在這里調查兩個有趣的問題:當前代碼有多少層深入堆棧?當前方法在達到a之前可以達到多少級別的遞歸StackOverflowError?當前執行代碼的堆棧深度這是我為此能想到的最好的方法:public static int levelsDeep() {    try {        throw new SomeKindOfException();    } catch (SomeKindOfException e) {        return e.getStackTrace().length;    }}這似乎有點駭人聽聞。它生成并捕獲異常,然后查看堆棧跟蹤的長度。不幸的是,它似乎也有一個致命的限制,即返回的堆棧跟蹤的最大長度為1024。超出此范圍的任何內容都會被削減,因此此方法可以返回的最大長度為1024。題:有沒有更好的方法來做到這一點,而且又沒有此限制?對于它的價值,我的猜測是沒有:Throwable.getStackTraceDepth()一個本機調用,這表明(但沒有證明)它不能用純Java完成。確定我們還剩下多少遞歸深度我們可以達到的級別數將由(a)堆棧框架的大小和(b)剩余堆棧數決定。讓我們不必擔心堆??蚣艿拇笮?,而只需在達到之前查看可以達到多少級StackOverflowError。這是我執行此操作的代碼:public static int stackLeft() {    try {        return 1+stackLeft();    } catch (StackOverflowError e) {        return 0;    }}即使堆棧數量是線性的,它的工作也令人欽佩。但是,這是非常非常奇怪的部分。在64位Java 7(OpenJDK 1.7.0_65)上,結果是完全一致的:在我的機器上(Ubuntu 14.04 64位),結果為9,923。但是Oracle的Java 8(1.8.0_25)給了我不確定的結果:記錄下來的深度大約在18500到20700之間。現在,為什么到底是不確定的?應該有固定的堆棧大小,不是嗎?而且所有代碼對我來說都是確定性的。我想知道錯誤陷阱是否有些怪異,所以我嘗試了一下:public static long badSum(int n) {    if (n==0)        return 0;    else        return 1+badSum(n-1);}顯然,這將返回給定的輸入或溢出。同樣,我在Java 8上獲得的結果是不確定的。如果調用badSum(14500),它將給我StackOverflowError大約一半的時間,而另一半則返回14500。但是在Java 7 OpenJDK上,它是一致的:badSum(9160)正常運行并badSum(9161)溢出。題:為什么在Oracle Java 8上不確定最大遞歸深度?為什么在OpenJDK 7上具有確定性?
查看完整描述

3 回答

?
FFIVE

TA貢獻1797條經驗 獲得超6個贊

觀察到的行為受到HotSpot優化器的影響,但這不是唯一的原因。當我運行以下代碼


public static void main(String[] argv) {

    System.out.println(System.getProperty("java.version"));

    System.out.println(countDepth());

    System.out.println(countDepth());

    System.out.println(countDepth());

    System.out.println(countDepth());

    System.out.println(countDepth());

    System.out.println(countDepth());

    System.out.println(countDepth());

}

static int countDepth() {

    try { return 1+countDepth(); }

    catch(StackOverflowError err) { return 0; }

}

啟用JIT后,我得到如下結果:


> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X

1.8.0_40-ea

2097

4195

4195

4195

12587

12587

12587


> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X

1.8.0_40-ea

2095

4193

4193

4193

12579

12579

12579


> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X

1.8.0_40-ea

2087

4177

4177

12529

12529

12529

12529

在這里,JIT的效果清晰可見,顯然,優化后的代碼需要更少的堆??臻g,并且它表明啟用了分層編譯(實際上,-XX:-TieredCompilation如果程序運行時間足夠長,則使用一次跳轉)。


相反,在禁用JIT的情況下,我得到以下結果:


> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X

1.8.0_40-ea

2104

2104

2104

2104

2104

2104

2104


> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X

1.8.0_40-ea

2076

2076

2076

2076

2076

2076

2076


> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X

1.8.0_40-ea

2105

2105

2105

2105

2105

2105

2105

這些值仍會變化,但不會在單個運行時線程內變化,并且幅度較小。


因此,如果優化程序可以減少每次方法調用所需的堆??臻g(例如,由于內聯),則存在(相當小的)差異,該差異會變得更大。


是什么導致這種差異?我不知道這個JVM是如何做到的,但是一種情況可能是強制執行堆棧限制的方式要求對堆棧結束地址進行一定的對齊(例如,匹配內存頁面大?。?,而內存分配返回的內存具有一個起始地址,對齊保證較弱。將這種情況與ASLR結合使用,可能在對齊要求的大小范圍內始終存在差異。


查看完整回答
反對 回復 2019-11-04
?
茅侃侃

TA貢獻1842條經驗 獲得超22個贊

它的過時,但你可以嘗試Thread.countStackFrames()像


public static int levelsDeep() {

    return Thread.currentThread().countStackFrames();       

}

根據Javadoc,


不推薦使用。此調用的定義取決于suspend()已棄用的。此外,此調用的結果從未明確定義。


計算此線程中的堆棧幀數。線程必須被掛起。


至于為什么您觀察到不確定性行為,我只能假設它是JIT和垃圾收集器的某種組合。


查看完整回答
反對 回復 2019-11-04
?
繁花不似錦

TA貢獻1851條經驗 獲得超4個贊

您無需捕獲異常,只需創建一個如下所示的異常:


新的Throwable()。getStackTrace()


要么:


Thread.currentThread()。getStackTrace()


由于JVM實現是特定的,因此它仍然很笨拙。JVM可能會決定修剪結果以獲得更好的性能。


查看完整回答
反對 回復 2019-11-04
  • 3 回答
  • 0 關注
  • 446 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號