[英]Understanding the Java stack
有這個代碼:
public class Main {
public static void main(final String[] args) throws Exception {
System.out.print("1");
doAnything();
System.out.println("2");
}
private static void doAnything() {
try {
doAnything();
} catch (final Error e) {
System.out.print("y");
}
}
}
還有輸出:
1yyyyyyyy2
為什么它打印“y”八次而不再打印。 遇到StackOverflowError
時,Java如何調用println()
?
在這里你捕獲Error
而不是Exception
在這種情況下你的程序會崩潰。
如果您嘗試此代碼(修改為添加靜態計數器)
public class StackError {
static int i = 1;
public static void main(final String[] args) throws Exception {
System.out.print("1");
doAnything();
System.out.println("2");
}
private static void doAnything() {
try {
i++;
// System.out.println(i);
doAnything();
} catch (Error e) {
System.out.print("y"+i+"-");
}
}
}
1y6869-2
因此,它有stackerror
6869次(不同運行的更改),並打印最后一個值。 如果您只是像之前那樣打印y
,那么輸出可能會緩沖並且不會被刷新,因為它不是println
。
System.out.println
內部調用緩沖的PrintStream
。 您不會從緩沖區中丟失任何數據,它會在填滿之后或在您明確調用flush時將所有數據寫入輸出(在您的情況下為終端)。
回到這個場景,它取決於堆棧填充多少的內部動態以及doAnything()
catch的執行數量,並且這些字符數被寫入緩沖區。 在主背面,它最終印有數字2
。
我敢打賭,通過在catch塊中調用print
,可以強制執行外部塊捕獲的另一個 StackOverflowError
。 其中一些調用沒有足夠的堆棧來實際寫入輸出流。
JLS說:
請注意,由於本機方法執行或Java虛擬機資源限制,可能會通過方法調用以及異步方式同步拋出StackOverflowError。
Java SE平台允許在拋出異步異常之前進行少量但有限的執行。
上面提到的延遲允許優化代碼在遵循Java編程語言的語義的同時檢測並拋出這些異常。 一個簡單的實現可能會在每個控制傳輸指令的點處輪詢異步異常。 由於程序具有有限大小,因此這提供了檢測異步異常的總延遲的界限。
第一次發生StackOverFlowError
時,將取消對最后一個doAnything()
的調用,並將控件從最后一個doAnything()
返回到catch塊。
但是,因為堆棧仍然幾乎已滿,調用System.out.print("y")
的簡單事實將導致另一個StackOverflowError
因為需要在堆棧上推送一些值然后調用函數print()
。
因此,再次發生另一個StackOverflowError
,現在返回上一個doAnything()
的catch {}塊返回; 其中會發生另一個StackOverflowError
,因為對System.out.println("y")
執行單個調用所需的堆棧空間大於從doAnything()
返回調用所釋放的空間量。
只有當堆棧上有足夠的空間來執行對System.out.print("y")
的調用時,此進程才會停止並且catch塊將成功完成。 我們可以通過運行以下代碼看到:
public class Principal3b {
static int a = 0;
static int i = 0;
static int j = 0;
public static void main(String[] args) {
System.out.println("X");
doAnything();
System.out.println("Y");
System.out.println(i);
System.out.println(j);
}
private static void doAnything() {
a++;
int b = a;
try {
doAnything();
} catch (final Error e) {
i++;
System.out.println(a);
System.out.println(b);
j++;
}
}
}
注意,使用println(a)
代替print(a)
; 因此,一個新行的每一個值后打印a
,如果一切運行正常。
但是,當我運行它時,我得到以下結果:
X
62066206620662066206620662066206
6190
Y
17
1
這意味着已經有17次嘗試運行catch塊。 在這些catch塊執行中,9個在生成StackOverflowError之前無法打印任何內容。 7能夠打印6190的值,但是在它們自己再次出現錯誤之前無法打印換行符,最后,有一個能夠打印6190的值和后面的換行符; 因此,最終允許其catch塊在沒有任何新的StackOverflowError的情況下完成,並優雅地返回調用堆棧。
當我們處理StackOverflowError時,這些數字只是一個例子,不僅在機器之間而且在執行之間變化很大,添加或刪除任何類型的指令的簡單事實也應該改變這些值。 但是,這里看到的模式應該保持不變。
有一點很清楚System.out.print(“y”); 在catch中創造了這個難題。 如果我們將代碼更改為
static int n;
public static void main(final String[] args) throws Exception {
System.out.println("1");
doAnything();
System.out.println(n);
}
private static void doAnything() {
try {
doAnything();
} catch (Error e) {
n++;
}
}
它打印
1
1
好吧,沒有。 堆棧溢出錯誤被觸發的次數是未定義的。 但是,JVM允許您從StackOverflowError
錯誤中恢復並正常繼續執行系統。
通過以下代碼證明:
public class Really {
public static void main(final String[] args) throws Exception {
System.out.print("1");
doAnything();
System.out.println("2");
}
private static void doAnything() {
try {
throw new StackOverflowError();
//doAnything();
}
catch(final Error e){
System.out.print("y");
}
}
}
但請注意,正如@Javier所說,JVM同步或異步拋出StackOverflowError
(這意味着它可能被另一個線程拋出,可能是本機線程),這就是為什么不可能得到錯誤的堆棧跟蹤的原因。 沒有。 線程中遇到catch()
塊的次數是未定義的。
此外, Error
類型的對象不是Exceptions
對象,它們代表異常條件。 Errors
表示不是由程序錯誤引起的異常情況,通常在程序執行期間通常不會發生,例如JVM內存不足。 雖然它們共享一個共同的超類Throwable
,意味着兩者都可以拋出,但它可以放在一個catch
但通常不應該被捕獲,因為它們代表罕見的,難以處理的異常條件。
堆棧溢出。
您只是在異常時打印,同時程序會進入溢出狀態。
此時發生的情況取決於個別系統,內存等。
該計划的目的是什么?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.