堆棧跟蹤從何而來?

我認為,閱讀和理解堆棧跟蹤是每個程序員都必須具備的一項基本技能,以便有效地解決每種JVM語言的問題(另請參閱: 過濾日志中無關的堆棧跟蹤行和首先記錄引起異常的根本原因 )。 那么我們可以從一個小測驗開始嗎? 給定以下代碼,堆棧跟蹤中將出現哪些方法? 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#中,根據在bar() 重新拋出原始異常的方式,兩種答案都是可能的– throw e用再次拋出該異常的位置 bar()bar()覆蓋原始堆棧跟蹤(起源于foo() bar() )。 。 另一方面,裸' throw '關鍵字會重新引發異常,從而保持原始堆棧跟蹤。 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()僅創建異常,而不是引發異常,而是返回該異常對象。 然后從完全不同的方法引發此異常。 堆棧跟蹤現在將如何顯示? 令人驚訝的是,它仍然指向foo() ,就像從那里拋出異常一樣,與第一個示例完全相同:

Exception in thread 'main' java.lang.RuntimeExceptionat Main.foo(Main.java:7)at Main.main(Main.java:15)

您可能會問發生了什么事? 看起來當拋出異常時不是生成堆棧跟蹤,而是在創建異常對象時生成 。 在絕大多數情況下,這些動作都發生在同一位置,因此沒有人打擾。 許多新手Java程序員甚至都不知道可以創建一個異常對象并將其分配給變量或字段,甚至可以將其傳遞出去。

但是,異常堆棧跟蹤的真正來源是什么? 答案很簡單,來自Throwable.fillInStackTrace()方法!

public class Throwable implements Serializable {public synchronized native Throwable fillInStackTrace();//...
}

請注意,此方法不是final方法,這使我們可以進行一點修改。 我們不僅可以繞過堆棧跟蹤的創建并在沒有任何上下文的情況下引發異常,甚至可以完全覆蓋堆棧!

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 。 我一生中曾經看到過使用這種技術的庫來更快地引發異常。 因此,我編寫了一個快速的游標卡程序基準測試,以查看拋出正常的RuntimeException和未填充堆棧跟蹤的異常與普通方法的返回值之間的性能差異。 我使用遞歸運行具有不同堆棧跟蹤深度的測試:

public class StackTraceBenchmark extends SimpleBenchmark {@Param({'1', '10', '100', '1000'})public int threadDepth;public void timeWithoutException(int reps) throws InterruptedException {while(--reps >= 0) {notThrowing(threadDepth);}}private int notThrowing(int depth) {if(depth <= 0)return depth;return notThrowing(depth - 1);}//--------------------------------------public void timeWithStackTrace(int reps) throws InterruptedException {while(--reps >= 0) {try {throwingWithStackTrace(threadDepth);} catch (RuntimeException e) {}}}private void throwingWithStackTrace(int depth) {if(depth <= 0)throw new RuntimeException();throwingWithStackTrace(depth - 1);}//--------------------------------------public void timeWithoutStackTrace(int reps) throws InterruptedException {while(--reps >= 0) {try {throwingWithoutStackTrace(threadDepth);} catch (RuntimeException e) {}}}private void throwingWithoutStackTrace(int depth) {if(depth <= 0)throw new ExceptionFromHell();throwingWithoutStackTrace(depth - 1);}//--------------------------------------public static void main(String[] args) {Runner.main(StackTraceBenchmark.class, new String[]{'--trials', '1'});}}

結果如下:

我們可以清楚地看到,堆棧跟蹤越長,拋出異常所花費的時間就越長。 我們還看到,對于合理的堆棧跟蹤長度,拋出異常的時間不應超過100?s(比讀取1 MiB主內存快)。 最終,在沒有堆棧跟蹤的情況下拋出異常的速度提高了2-5倍。 但老實說,如果這對您來說是個問題,那么問題就出在別的地方。 如果您的應用程序經常拋出異常而實際上必須對其進行優化,則您的設計可能存在問題。 然后不要修復Java,它不會損壞。

摘要:

  • 堆棧跟蹤始終顯示創建異常(對象)的位置,而不是引發異常的位置-盡管在99%的情況下,該位置相同。
  • 您可以完全控制由異常返回的堆棧跟蹤
  • 生成堆棧跟蹤會帶來一些成本,但是如果它成為應用程序的瓶頸,則可能是您做錯了什么。

參考: 堆棧跟蹤來自何處? 來自我們的JCG合作伙伴 Tomasz Nurkiewicz,來自Java和鄰里博客。


翻譯自: https://www.javacodegeeks.com/2012/10/where-do-stack-traces-come-from.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/371358.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/371358.shtml
英文地址,請注明出處:http://en.pswp.cn/news/371358.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

@select 怎么寫存儲過程_MySQL4:存儲過程和函數

什么是存儲過程簡單說&#xff0c;存儲過程就是一條或多條SQL語句的集合&#xff0c;可視為批文件&#xff0c;但是起作用不僅限于批處理。本文主要講解如何創建存儲過程和存儲函數以及變量的使用&#xff0c;如何調用、查看、修改、刪除存儲過程和存儲函數等。使用的數據庫和表…

跨域訪問-預請求及跨域常見問題

預請求 參考&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#預請求 簡而言之&#xff0c;在跨域并且嘗試添加一些特殊頭及自定義頭的情況下&#xff0c;由于瀏覽器的安全機制&#xff0c;會加多一次OPTIONS預請求&#xff08;詢問請求&am…

mysql查詢優化之一:mysql查詢優化常用方式

一、為什么查詢速度會慢&#xff1f; 一個查詢的生命周期大致可以按照順序來看&#xff1a;從客戶端&#xff0c;到服務器&#xff0c;然后在服務器上進行解析&#xff0c;生成執行計劃&#xff0c;執行&#xff0c;并返回結果給客戶端。其中在“執行”階段包含了大量為了檢索…

修復Sonar中常見的Java安全代碼沖突

本文旨在向您展示如何快速修復最常見的Java安全代碼沖突。 它假定您熟悉代碼規則和違規的概念以及Sonar如何對其進行報告。 但是&#xff0c;如果您以前從未聽說過這些術語&#xff0c;則可以閱讀Sonar Concepts或即將出版的有關Sonar的書 &#xff0c;以獲取更詳細的解釋。 為…

理解ThreadLocal

ThreadLocal:線程本地存儲&#xff0c;為每個線程都創建了變量的副本&#xff0c;線程在訪問變量時&#xff0c;可以直接訪問自己內部的副本變量。 理解幾個概念&#xff1a; 在java中ThreadLocal是一個類。 ThreadMap是一個類&#xff0c; Thread類是線程類。 ThreadLocal…

抖音右上角一個小黃點是什么_抖音官方入駐視頻號,釋放了一個什么樣的信號?...

專注視頻號觀察第 328 篇這幾天&#xff0c;視頻號生態新入駐了一個企業號&#xff0c;在圈里引起不少的轟動&#xff0c;因為這個號的名字叫做———抖音。這件事在圈里引發不少的轟動&#xff0c;很多人驚嘆&#xff1a;“連抖音都來開視頻號了&#xff0c;你還在等什么&…

資源包技巧和最佳實踐

今天是資源捆綁日。 通常&#xff0c;這是Java中最著名的國際化機制&#xff08;i18n&#xff09;。 使用它應該很容易。 但是&#xff0c;在弄臟手時會出現許多小問題。 如果您有相同的想法&#xff0c;則此文章適合您。 基本 java.util.ResourceBundle定義了用于訪問Java中翻…

springMvc-文件上傳

趕時間&#xff0c;又在寫垃圾博客&#xff0c;在心里給自己一耳巴 1.單文件上傳 2.多文件上傳 代碼&#xff1a; 頁面&#xff1a; <!DOCTYPE html><html><head><meta charset"UTF-8"><title>Insert title here</title></he…

c cuda 指定gpu_GPU并行編程:熟練使用CUDA C語言

【IT168 專稿】一個大任務通常可能被分解成許多可以一起處理的小任務&#xff0c;以便創建一個解決方案&#xff0c;這和粉刷房子的道理是一樣的&#xff0c;在粉刷之前&#xff0c;假設你需要買5公升油漆和5把刷子&#xff0c;你可以自己一個人干完采購和粉刷的活&#xff0c;…

js中使用0 “” null undefined {}需要注意

注意&#xff1a;在js中0為空&#xff08;false&#xff09; &#xff0c;代表空的還有“”&#xff0c;null &#xff0c;undefined&#xff1b; 如果做判斷if(&#xff01;上面的四種值)&#xff1b;返回均為false console.log(!null);// true console.log(!0);//true consol…

PhpStorm 10.0.3破解版下載

漢化破解版軟件下載&#xff1a; http://pan.baidu.com/s/1geNO24r 密碼: d5ci 這個漢化破解軟件解決了大綱視圖里空白的問題。 先安裝騰訊電腦管家&#xff0c;然后安裝這個軟件&#xff0c;安裝到最后提示有個文件有病毒已刪除&#xff0c;點確定后正常使用。轉載于:https://…

Jenkins:部署JEE工件

隨著持續集成和持續交付的出現 &#xff0c;我們的構建被分為不同的步驟&#xff0c;以創建部署管道。 這些步驟中的一些步驟可以是例如編譯和運行快速測試&#xff0c;運行慢速測試&#xff0c;運行自動驗收測試或發布應用程序等。 部署流程的最后一步意味著將我們的產品&…

seafile 部署_Seafile開啟webdav及讀寫性能測試

為什么要在seafile搞webdavSeafile 一直是一款可靠的文件同步web應用&#xff0c;經過個人測試&#xff0c;同一臺機器上&#xff0c;seafile在傳輸文件時的速度比nextcloud要快&#xff08;可能也與php的設置有關系&#xff09;&#xff0c;這是seafile的優勢。但是&#xff0…

Python--校園網爬蟲記

查成績&#xff0c;算分數&#xff0c;每年的綜合測評都是個固定的過程&#xff0c;作為軟件開發者&#xff0c;這些過程當然可以交給代碼去做&#xff0c;通過腳本進行網絡請求獲取數據&#xff0c;然后直接進行計算得到基礎分直接填表就好了&#xff0c;查成績再手動計算既容…

Spring–添加SpringMVC –第1部分

歡迎來到本教程的第四部分。 在這一部分中&#xff0c;我們將使用Spring MVC編寫控制器和視圖&#xff0c;并考慮我們的REST模型。 我們必須做的第一件事&#xff0c;就是根據目前的情況制作一個Web應用程序。 我們將web / WEB-INF文件夾添加到我們的項目根目錄。 在WEB-INF內創…

[Linux] 權限與指令間的關系

我們知道權限對于使用者帳號來說是非常重要的&#xff0c;因為他可以限制使用者能不能讀取/創建/刪除/修改文件或目錄&#xff01; 在這一章我們介紹了很多文件系統的管理指令&#xff0c;第五章則介紹了很多文件權限的意義。在這個小節當中&#xff0c; 我們就將這兩者結合起來…

access month函數用法_學會了這7個EXCEL日期函數技巧,老板再讓你加班,你找我!...

日期函數&#xff0c;常用年月日&#xff0c;時分秒&#xff0c;星期&#xff0c;季度&#xff0c;求差值等&#xff0c;學會以下幾個函數&#xff0c;老板再讓你加班&#xff0c;你找我&#xff01;1、記錄當前時間(不隨系統時間變化)NOW()函數與數據有效性結合&#xff0c;記…

css樣式表的選擇器與分類

css 樣式表的作用&#xff1a; 主要用于結構,樣式與行為,CSS主要的作用就是美化網頁的一個語言,它的特點: 1.結構與樣式分離的方式,便于后期維護與改版; 2.樣式定義精確到像素的級別; css樣式表的結構&#xff1a;CSS 稱為層疊樣式表 用于給網頁設置各種樣式 css樣式的語法由3部…

Spring 3.1緩存和@Cacheable

緩存在軟件領域已經存在很長時間了。 它們是那些真正有用的東西之一&#xff0c;一旦您開始使用它們&#xff0c;您會想知道如果沒有它們&#xff0c;您是如何相處的&#xff0c;所以似乎讓Spring的家伙們只是在版本中向Spring核心添加緩存實現有點奇怪。 3.1。 我猜想以前沒有…

pytorchyolov4訓練_使用pytorch-yolov5 訓練自己的數據集-2020.6.15

make yolov5 pytorch train datasets訓練所需環境 python3.5, pytorch1.3, torchvision 0.4.1 , tensorboard 1.14.0 , tensorflow-gpu1.14.0本例制作yolov5數據集 并進行數據訓練從VOC數據集轉為訓練所需的coco數據集代碼有待改進包含文件夾voc2coco/(Annotations/ JPEGImages…