内核堆栈 用户堆栈_堆栈痕迹从何而来?
內核堆棧 用戶堆棧
我相信,閱讀和理解堆棧跟蹤是每個程序員都必須具備的一項基本技能,以便有效地解決每種JVM語言的問題(另請參閱: 過濾日志中無關的堆棧跟蹤行和首先記錄引起根的異常 )。 那么我們可以從一個小測驗開始嗎? 給定以下代碼,堆棧跟蹤中將出現(xiàn)哪些方法? foo() , bar()還是兩者皆有? public class Main {public static void main(String[] args) throws IOException {try {foo();} catch (RuntimeException e) {bar(e);}}private static void foo() {throw new RuntimeException('Foo!');}private static void bar(RuntimeException e) {throw e;} }在C#中,根據(jù)在bar() 重新拋出原始異常的方式,兩種答案都是可能的– throw e用再次拋出該異常的位置 bar()在bar()覆蓋原始堆棧跟蹤(起源于foo() bar() )。 。 另一方面,裸' throw '關鍵字會重新引發(fā)異常,從而保持原始堆棧跟蹤。 Java遵循第二種方法(使用第一種方法的語法),甚至不允許直接使用前一種方法。 但是這個經過稍微修改的版本呢:
public static void main(String[] args) throws IOException {final RuntimeException e = foo();bar(e); }private static RuntimeException foo() {return new RuntimeException(); }private static void bar(RuntimeException e) {throw e; } foo()僅創(chuàng)建異常,而不是拋出異常,而是返回該異常對象。 然后從完全不同的方法引發(fā)此異常。 堆棧跟蹤現(xiàn)在將如何顯示? 令人驚訝的是,它仍然指向foo() ,就像從那里拋出異常一樣,與第一個示例完全相同:
您可能會問發(fā)生了什么事? 看起來當拋出異常時不是生成堆棧跟蹤,而是在創(chuàng)建異常對象時生成 。 在絕大多數(shù)情況下,這些動作都發(fā)生在同一位置,因此沒有人打擾。 許多新手Java程序員甚至都不知道可以創(chuàng)建一個異常對象并將其分配給變量或字段,甚至可以將其傳遞出去。
但是,異常堆棧跟蹤的真正來源是什么? 答案很簡單,來自Throwable.fillInStackTrace()方法!
public class Throwable implements Serializable {public synchronized native Throwable fillInStackTrace();//... }請注意,此方法不是final方法,這使我們可以進行一點修改。 我們不僅可以繞過堆棧跟蹤的創(chuàng)建并在沒有任何上下文的情況下引發(fā)異常,甚至可以完全覆蓋堆棧!
public class SponsoredException extends RuntimeException {@Overridepublic synchronized Throwable fillInStackTrace() {setStackTrace(new StackTraceElement[]{new StackTraceElement('ADVERTISEMENT', ' If you don't ', null, 0),new StackTraceElement('ADVERTISEMENT', ' want to see this ', null, 0),new StackTraceElement('ADVERTISEMENT', ' exception ', null, 0),new StackTraceElement('ADVERTISEMENT', ' please buy ', null, 0),new StackTraceElement('ADVERTISEMENT', ' full version ', null, 0),new StackTraceElement('ADVERTISEMENT', ' of the program ', null, 0)});return this;} }public class ExceptionFromHell extends RuntimeException {public ExceptionFromHell() {super('Catch me if you can');}@Overridepublic synchronized Throwable fillInStackTrace() {return this;} }拋出上述異常將導致JVM打印以下錯誤(嚴重時,請嘗試一下!)
Exception in thread 'main' SponsoredExceptionat ADVERTISEMENT. If you don't (Unknown Source)at ADVERTISEMENT. want to see this (Unknown Source)at ADVERTISEMENT. exception (Unknown Source)at ADVERTISEMENT. please buy (Unknown Source)at ADVERTISEMENT. full version (Unknown Source)at ADVERTISEMENT. of the program (Unknown Source)Exception in thread 'main' ExceptionFromHell: Catch me if you can 那就對了。 ExceptionFromHell更加有趣。 由于它不將堆棧跟蹤作為異常對象的一部分包括在內,因此僅類名和消息可用。 堆棧跟蹤丟失了,JVM和任何日志記錄框架都無法對此做任何事情。 你到底為什么會這樣做(我不是在談論SponsoredException )?
意外地,某些(?)認為生成堆棧跟蹤很昂貴,這是一種native方法,它必須遍歷整個堆棧才能構建StackTraceElement 。 一生中,我看到一個使用此技術的庫來更快地引發(fā)異常。 因此,我編寫了一個快速的游標卡程序基準測試,以查看拋出正常的RuntimeException和未填充堆棧跟蹤的異常與普通方法的返回值之間的性能差異。 我使用遞歸運行具有不同堆棧跟蹤深度的測試:
結果如下:
我們可以清楚地看到,堆棧跟蹤越長,拋出異常所花費的時間就越長。 我們還看到,對于合理的堆棧跟蹤長度,拋出異常的時間不應超過100?s(比讀取1 MiB主內存快)。 最終,在沒有堆棧跟蹤的情況下引發(fā)異常的速度提高了2-5倍。 但老實說,如果這對您來說是個問題,那么問題就出在別的地方。 如果您的應用程序經常拋出異常而實際上必須對其進行優(yōu)化,則您的設計可能存在問題。 然后不要修復Java,它不會損壞。
摘要:
- 堆棧跟蹤始終顯示創(chuàng)建異常(對象)的位置,而不是引發(fā)異常的位置-盡管在99%的情況下,該位置相同。
- 您可以完全控制由異常返回的堆棧跟蹤
- 生成堆棧跟蹤會花費一些成本,但是如果它成為應用程序的瓶頸,則可能是您做錯了什么。
參考: 堆棧跟蹤來自何處? 來自我們的JCG合作伙伴 Tomasz Nurkiewicz,來自Java和鄰里博客。
翻譯自: https://www.javacodegeeks.com/2012/10/where-do-stack-traces-come-from.html
內核堆棧 用戶堆棧
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
以上是生活随笔為你收集整理的内核堆栈 用户堆栈_堆栈痕迹从何而来?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 气壮山什么 气壮山什么的四字成语
- 下一篇: 专访腾讯多媒体实验室商世东:咬咬牙也要跟