成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

“崩潰了?不可能,我全 Catch 住了” | Java 異常處理

stdying / 2969人閱讀

摘要:允許存在多個,用于針對不同的異常做不同的處理。表示程序可能需要捕獲并且處理的異常。因此,我們應該盡可能的避免通過異常來處理正常的邏輯檢查,這樣可以確保不會因為發(fā)生異常而導致性能問題。異常表中的每一條記錄,都代表了一個異常處理器。

前言

今天我們來討論一下,程序中的錯誤處理。

在任何一個穩(wěn)定的程序中,都會有大量的代碼在處理錯誤,有一些業(yè)務錯誤,我們可以通過主動檢查判斷來規(guī)避,可對于一些不能主動判斷的錯誤,例如 RuntimeException,我們就需要使用 try-catch-finally 語句了。

有人說,錯誤處理并不難啊,try-catch-finally 一把梭,try 放功能代碼,在 catch 中捕獲異常、處理異常,finally 中寫那些無論是否發(fā)生異常,都要執(zhí)行的代碼,這很簡單啊。

處理錯誤的代碼,確實并不難寫,可是想把錯誤處理寫好,也并不是一件容易的事情。

接下來我們就從實現(xiàn)到 JVM 原理,講清楚 Java 的異常處理。

學東西,我還是推薦要帶著問題去探索,提前思考幾個問題吧:

一個方法,異常捕獲塊中,不同的地方的 return 語句,誰會生效?

catch 和 finally 中出現(xiàn)異常,會如何處理?

try-catch 是否影響效率?

Java 異常捕獲的原理?

二、Java 異常處理 2.1 概述

既然是異常處理,肯定是區(qū)分異常發(fā)生捕獲、處理異常,這也正是組成異常處理的兩大要素。

在 Java 中,拋出的異??梢苑譃?strong>顯示異常和隱式異常,這種區(qū)分主要來自拋出異常的主體是什么,顯示和隱式也是站在應用程序的視角來區(qū)分的。

顯示異常的主體是當前我們的應用程序,它指的是在應用程序中使用 “throw” 關(guān)鍵字,主動將異常實例拋出。而隱式異常就不受我們控制, 它觸發(fā)的主體是 Java 虛擬機,指的是 Java 虛擬機在執(zhí)行過程中,遇到了無法繼續(xù)執(zhí)行的異常狀態(tài),續(xù)而將異常拋出。

對于隱式異常,在觸發(fā)時,需要顯示捕獲(try-catch),或者在方法頭上,用 "throw" 關(guān)鍵字聲明,交由調(diào)用者捕獲處理。

2.2 使用異常捕獲

在我們編寫異常處理代碼的時候,主要就是使用前面介紹到的 try-catch-finally 這三種代碼塊。

try 代碼塊:包含待監(jiān)控異常的代碼。

catch 代碼塊:緊跟 try 塊之后,可以指定異常類型。允許指定捕獲多種不同的異常,catch 塊用來捕獲在 try 塊中出發(fā)的某個指定類型的異常。

finally 代碼塊:緊跟 try 塊或 catch 塊之后,用來聲明一段必定會運行的代碼。例如用來清理一些資源。

catch 允許存在多個,用于針對不同的異常做不同的處理。如果使用 catch 捕獲多種異常,各個 catch 塊是互斥的,和 switch 語句類似,優(yōu)先級是從上到下,只能選擇其一去處理異常。

既然 try-catch-finally 存在多種情況,并且在發(fā)生異常和不發(fā)生異常時,表現(xiàn)是不一致的,我們就分清楚來多帶帶分析。

1. try塊中,未發(fā)生異常

不觸發(fā)異常,當然是我們樂于看見的。在這種情況下,如果有 finally 塊,它會在 try 塊之后運行,catch 塊永遠也不會被運行。

2. try塊中,發(fā)生異常

在發(fā)生異常時,會首先檢查異常類型,是否存在于我們的 catch 塊中指定的待捕獲異常。如果存在,則這個異常被捕獲,對應的 catch 塊代碼則開始運行,finally 塊代碼緊隨其后。

例如:我們只監(jiān)聽了空指針(NullPointerException),此時如果發(fā)生了除數(shù)為 0 的崩潰(ArithmeticException),則是不會被處理的。

當觸發(fā)了我們未捕獲的異常時,finally 代碼依然會被執(zhí)行,在執(zhí)行完畢后,繼續(xù)將異?!皰伋鋈ァ薄?/p>

3. catch 或者 finally 發(fā)生異常

catch 代碼塊和 finally 代碼塊,也是我們編寫的,理論上也是有出錯的可能。

那么這兩段代碼發(fā)生異常,會出現(xiàn)什么情況呢?

當在 catch 代碼塊中發(fā)生異常時,此時的表現(xiàn)取決于 finally 代碼塊中是否存在 return 語句。如果存在,則 finally 代碼塊的代碼執(zhí)行完畢直接返回,否則會在 finally 代碼塊執(zhí)行完畢后,將 catch 代碼中新產(chǎn)生的異常,向外拋出去。

而在極端情況下,finally 代碼塊發(fā)生了異常,則此時會中斷 finally 代碼塊的執(zhí)行,直接將異常向外拋出。

2.3 異常捕獲的返回值

再回頭看看第一個問題,假如我們寫了一個方法,其中的代碼被 try-catch-finally 包裹住進行異常處理,此時如果我們在多個地方都有 return 語句,最終誰的會被執(zhí)行?

如上圖所示,在完整的 try-catch-finally 語句中,finally 都是最后執(zhí)行的,假設 finally 代碼塊中存在 return 語句,則直接返回,它是優(yōu)先級最高的。

一般我們不建議在 finally 代碼塊中添加 return 語句,因為這會破壞并阻止異常的拋出,導致不宜排查的崩潰。

2.4 異常的類型

在 Java 中,所有的異常,其實都是一個個異常類,它們都是 Throwable 類或其子類的實例。

Throwable 有兩大子類,ExceptionError。

Exception:表示程序可能需要捕獲并且處理的異常。

Error:表示當觸發(fā) Error 時,它的執(zhí)行狀態(tài)已經(jīng)無法恢復了,需要中止線程甚至是中止虛擬機。這是不應該被我們應用程序所捕獲的異常。

通常,我們只需要捕獲 Exception 就可以了。但 Exception 中,有一個特殊的子類 RuntimeException,即運行時錯誤,它是在程序運行時,動態(tài)出現(xiàn)的一些異常。比較常見的就是 NullPointerException、ArrayIndexOutOfBoundsException 等。

Error 和 RuntimeException 都屬于非檢查異常(Unchecked Exception),與之相對的就是普通 Exception 這種屬于檢查異常(Checked Exception)。

所有檢查異常都需要在程序中,用代碼顯式捕獲,或者在方法中用 throw 關(guān)鍵字顯式標注。其實意思很明顯,要不你自己處理了,要不你拋出去讓別人處理。

這種檢查異常的機制,是在編譯期間進行檢查的,所以如果不按此規(guī)范處理,在編譯器編譯代碼時,就會拋出異常。

2.5 異常處理的性能問題

對于異常處理的性能問題,其實是一個很有爭議的問題,有人覺得異常處理是多做了一些工作,肯定對性能是有影響的。但是也有人覺得異常處理的影響,和增加一個 if-else 屬于同種量級,對性能的影響其實微乎其微,是在可以接受的范圍內(nèi)的。

既然有爭議,最簡單的辦法是寫個 Demo 驗證一下。當然,我們這里是需要區(qū)分不同的情況,然后根據(jù)解決對比的。

一個最簡單的 for 循環(huán) 100w 次,在其中做一個 a++ 的自增操作。

A:無任何 try-catch 語句。

B:將 a++ 包在 try 代碼塊中。

C:在 try 代碼塊中,觸發(fā)一個異常。

就是一個簡單的 for 循環(huán),就不貼代碼了,異常通過 5/0 這樣的運算,觸發(fā)除數(shù)為 0 的 ArithmeticException 異常,并在 JDK 1.8 的環(huán)境下運行。

為了避免影響采樣結(jié)果,每個例子都多帶帶運行 10 遍之后,取平均值(單位納秒)。

到這里基本上就可以得出結(jié)論了,在沒有發(fā)生異常的情況下,try-catch 對性能的影響微乎其微。但是一旦發(fā)生異常,性能上則是災難性的。

因此,我們應該盡可能的避免通過異常來處理正常的邏輯檢查,這樣可以確保不會因為發(fā)生異常而導致性能問題。

至于為什么發(fā)生異常時,性能差別會有如此之大,就需要從 Java 虛擬機 JVM 的角度來分析了,后面會詳細分析。

2.6 異常處理無法覆蓋異步回調(diào)

try-catch-finally 確實很好用,但是它并不能捕獲,異步回調(diào)中的異常。try 語句里的方法,如果允許在另外一個線程中,其中拋出的異常,是無法在調(diào)用者這個線程中捕獲的。

這一點在使用的過程中,需要特別注意。

三、JVM 如何處理異常 3.1 JVM 異常處理概述

接下來我們從 JVM 的角度,分析 JVM 如何處理異常。

當異常發(fā)生時,異常實例的構(gòu)建,是非常消耗性能的。這是由于在構(gòu)造異常實例時,Java 虛擬機需要生成該異常的異常棧(stack trace)。

異常棧會逐一訪問當前線程的 Java 棧幀,以及各種調(diào)試信息。包括棧幀所指向的方法名,方法所在的類名、文件名以及在代碼中是第幾行觸發(fā)的異常。

這些異常輸出到 Log 中,就是我們熟悉的崩潰日志(崩潰棧)。

3.2 崩潰實例分析異常處理

當把 Java 代碼編譯成字節(jié)碼后,每個方法都會附帶一個異常表,其中記錄了當前方法的異常處理。

下面直接舉個例子,寫一個最簡單的 try-catch 類。

使用 javap -c 進行反編譯成字節(jié)碼。

可以看到,末尾的 Exceptions Table 就是異常表。異常表中的每一條記錄,都代表了一個異常處理器。

異常處理器中,標記了當前異常監(jiān)控的起始、結(jié)束代碼索引,和異常處理器的索引。其中 from 指針和 to 指針標識了該異常處理器所監(jiān)控的代碼范圍,target 指針則指向異常處理器的起始位置,type 則為最后監(jiān)聽的異常。

例如上面的例子中,main 函數(shù)中存在異常表,Exception 的異常監(jiān)聽代碼范圍分別是 [0,8)(不包括 8),異常處理器的索引為 11。

繼續(xù)分析異常處理流程,還需要區(qū)分是否命中異常。

1. 命中異常

當程序發(fā)生異常時,Java 虛擬機會從上到下遍歷異常表中所有的記錄。當發(fā)現(xiàn)觸發(fā)異常的字節(jié)碼的索引值,在某個異常表中某個異常監(jiān)控的范圍內(nèi)。Java 虛擬機會判斷所拋出的異常和該條異常監(jiān)聽的異常類型,是否匹配。如果能匹配上,Java 虛擬機會將控制流轉(zhuǎn)向至該此異常處理器的 target 索引指向的字節(jié)碼,這是命中異常的情況。

2. 未命中異常

而如果遍歷完異常表中所有的異常處理器之后,仍未匹配到異常處理器,那么它會彈出當前方法對應的 Java 棧幀?;氐剿恼{(diào)用者,在其中重復此過程。

最壞的情況下,Java 虛擬機需要遍歷當前線程 Java 棧上所有方法的異常表。

3.3 編譯后的 finally 代碼塊

我們寫的代碼,其實終歸是給人讀的,但是編譯器干的事兒,都不是人事兒。它會把代碼做一些特殊的處理,只是為了讓自己更好解析和執(zhí)行。

編譯器對 finally 代碼塊,就是這樣處理的。在當前版本的 Java 編譯器中,會將 finally 代碼塊的內(nèi)容,復制幾份,分別放在所有可能執(zhí)行的代碼路徑的出口中。

寫個 Demo 驗證一下,代碼如下。

繼續(xù) javap -c 反編譯成字節(jié)碼。

這個例子中,為了更清晰的看到 finally 代碼塊,我在其中輸出的一段 Log “run finally”??梢钥吹剑幾g結(jié)果中,包含了三份 finally 代碼塊。

其中,前兩份分別位于 try 代碼塊和 catch 代碼塊的正常執(zhí)行路徑出口。最后一份則作為全局的異常處理器,監(jiān)控 try 代碼塊以及 catch 代碼塊。它將捕獲 try 代碼塊觸發(fā)并且未命中 catch 代碼塊捕獲的異常,以及在 catch 代碼塊觸發(fā)的異常。

而 finally 的代碼,如果出現(xiàn)異常,就不是當前方法所能處理的了,會直接向外拋出。

3.4 異常表中的 any 是什么?

從上圖中可以看到,在異常表中,還存在兩個 any 的信息。

第一個信息的 from 和 to 的范圍就是 try 代碼塊,等于是對 catch 遺漏異常的一種補充,表示會處理所有種類的異常。

第二個信息的 from 和 to 的范圍,仔細看能看到它其實是 catch 代碼塊,這也正好印證了我們上面的結(jié)論,catch 代碼塊其實也被異常處理器監(jiān)控著。

只是如果命中了 any 之后,因為沒有對應的異常處理器,會繼續(xù)向上拋出去,交由該方法的調(diào)用方法處理。

四、總結(jié)

到這里我們就基本上講清楚了 Java 異常處理的所有內(nèi)容。

在日常開發(fā)當中,應該盡量避免使用異常處理的機制來處理業(yè)務邏輯,例如很多代碼中,類型轉(zhuǎn)換就使用 try-catch 來處理,其實是很不可取的。

異常捕獲對應用程序的性能確實有影響,但也是分情況的。

一旦異常被拋出來,方法也就跟著 return 了,捕獲異常棧時會導致性能變得很慢,尤其是調(diào)用棧比較深的時候。

但是從另一個角度來說,異常拋出時,基本上表明程序的錯誤。應用程序在大多數(shù)情況下,應該是在沒有異常情況的環(huán)境下運行的。所以,異常情況應該是少數(shù)情況,只要我們不濫用異常處理,基本上不會影響正常處理的性能問題。

本文對你有幫助嗎?留言、點贊、轉(zhuǎn)發(fā)是最大的支持,謝謝!

公眾號后臺回復成長『成長』,將會得到我準備的學習資料,也能回復『加群』,一起學習進步;你還能回復『提問』,向我發(fā)起提問。

推薦閱讀:

“寒冬”正是學習時|關(guān)于字符編碼,你需要知道的都在這里 | 分詞,科普及解決方案| 圖解:HTTP 范圍請求 | 小程序?qū)W習資料 |HTTP 內(nèi)容編碼 | 輔助模式實戰(zhàn) | 輔助模式玩出花樣 | 小程序 Flex 布局

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/73057.html

相關(guān)文章

  • 16.java異常處理

    摘要:不受檢查異常為編譯器不要求強制處理的異常,檢查異常則是編譯器要求必須處置的異常。潛在的異常處理器是異常發(fā)生時依次存留在調(diào)用棧中的方法的集合。當運行時系統(tǒng)遍歷調(diào)用棧而未找到合適的異常處理器,則運行時系統(tǒng)終止。異常處理涉及到五個關(guān)鍵字,分別是。 概念 程序運行時,發(fā)生的不被期望的事件,它阻止了程序按照程序員的預期正常執(zhí)行,這就是異常。 異常是程序中的一些錯誤,但并不是所有的錯誤都是異常,并...

    asce1885 評論0 收藏0
  • Java異常處理

    摘要:異常也就是指程序運行時發(fā)生錯誤,而異常處理就是對這些錯誤進行處理和控制。有兩個重要的子類異常和錯誤,二者都是異常處理的重要子類,各自都包含大量子類。需要注意的是,一旦某個捕獲到匹配的異常類型,將進入異常處理代碼。 1,異?,F(xiàn)象 程序錯誤分為三種:1,編譯錯誤;2,運行時錯誤;3,邏輯錯誤。 編譯錯誤是因為程序沒有遵循語法規(guī)則,編譯程序能夠自己發(fā)現(xiàn)并且提示我們錯誤的原因和位置,這...

    CarlBenjamin 評論0 收藏0
  • Java異常處理

    摘要:可以被異常處理機制使用,是異常處理的核心。非檢測異常,在編譯時,不會提示和發(fā)現(xiàn)異常的存在,不強制要求程序員處理這樣的異常??傮w來說,語言的異常處理流程,從程序中獲取異常信息。處理運行時異常,采用邏輯合理規(guī)避同時輔助處理。 目錄 什么是Java異常? 當一個Exception在程序中發(fā)生的時候,JVM是怎么做的呢? 當我們編寫程序的時候如何對待可能出現(xiàn)的異常呢? 正文 1. 什么是J...

    Fourierr 評論0 收藏0
  • Java 異常處理

    摘要:下面是異常處理機制的語法結(jié)構(gòu)業(yè)務實現(xiàn)代碼輸入不合法如果執(zhí)行塊里業(yè)務邏輯代碼時出現(xiàn)異常,系統(tǒng)自動生成一個異常對象,該對象被提交給運行時環(huán)境,這個過程被稱為拋出異常。 Java的異常機制主要依賴于try、catch、finally、throw和throws五個關(guān)鍵字, try關(guān)鍵字后緊跟一個花括號括起來的代碼塊(花括號不可省略),簡稱try塊,它里面放置可能引發(fā)異常的代碼 catch后對...

    senntyou 評論0 收藏0
  • java異常處理機制的理解

    摘要:根據(jù)異常對象判斷是否存在異常處理。否則,范圍小的異常會因異常處理完成而無法處理。異常處理中使用作為異常的統(tǒng)一出口。 參考《第一行代碼java》《java程序設計教程》java中程序的錯誤有語法錯誤、語義錯誤。如果是語法性錯誤,在編譯時就可以檢查出來并解決。語義錯誤是在程序運行時出現(xiàn)的,在編譯時沒有錯誤,但在運行時可能會出現(xiàn)錯誤導致程序退出,這些錯誤稱為異常。在沒有異常處理的情況下,也即...

    khs1994 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<