java的标量和聚合量_第5节:Java基础 - 必知必会(下)
第5節:Java基礎 - 必知必會(下)
本小節是Java基礎篇章的第三小節,主要講述Java中的Exception與Error,JIT編譯器以及值傳遞與引用傳遞的知識點。
一、Java中的Exception和Error有什么區別
Exception和Error的主要區別可以概括如下:
Exception是程序正常運行中預料到可能出現的錯誤,并且應該被捕獲并進行相應處理,是一種異常現象。
Error是正常情況下不可能發生的錯誤,Error會導致JVM處于已追蹤不可恢復的狀態,不需要捕獲處理,比如說OutOfMemoryError
解析:
Exception又分為了運行時異常和編譯異常。
編譯異常(受檢異常)表示當前調用的方法體內部拋出了一個異常,所以編譯器檢測到這段代碼在運行時可能會出現異常,所以我們必須對異常進行相應處理,可以捕獲異常或者拋給上層調用方。
運行時異常(非受檢異常)表示在運行時出現的異常,常見的運行異常包括:空指針異常,數組越界異常,數字轉換異常以及算數異常等。
前面說到了異常Exception應該被捕獲,我們可以使用try-catch-finally來處理異常,并且使得程序恢復正常。
那么我們捕獲異常應該遵循哪些基本原則呢?
盡可能捕獲比較詳細的異常,而不是使用Exception一起捕獲。
當本模塊不知道捕獲之后該怎么處理異常時,可以將其拋給上層模塊。上層模塊擁有更多的業務邏輯,可以進行更好的處理。
捕獲異常后至少應該有日志記錄,方便之后的排查。
不要使用一個很大的try-catch包住整段代碼,不利于問題的排查。
NoClassDefFoundError 和 ClassNotFoundException 有什么區別?
從名字中,我們可以看出前者是一個錯誤,后者是一個異常。我們先來看下JDK中對ClassNotFoundException異常的闡述:
大概意思就是在說,當我們使用例如Class.forName方法來動態的加載該類的時候,傳入了一個類名,但是其并沒有在類路徑中被找到的時候,就會報ClassNotFoundException異常。出現這種情況,一般都是類名字傳入有誤導致的。
我們再來看下JDK中對該錯誤NoClassDefFoundError的闡述:
大概意思是這樣的,如果JVM或者ClassLoader實例嘗試加載(可以通過正常的方法調用,也可能是使用new來創建新的對象)類的時候卻找不到類的定義。但是要查找的類在編譯的時候是存在的,運行的時候卻找不到了。這個時候就會導致NoClassDefFoundError。出現這種情況,一般是由于打包的時候漏掉了部分類或者Jar包被篡改已經損壞。
二、JIT編譯器
前面我們談到了Java是一種先編譯,后解釋執行的語言。那么我們就來說下何為JIT編譯器吧。
JIT編譯器全名叫Just In Time Compile也就是即時編譯器,把經常運行的代碼作為"熱點代碼"編譯成與本地平臺相關的機器碼,并進行各種層次的優化。JIT編譯除了具有緩存的功能外,還會對代碼做各種優化,包括逃逸分析、鎖消除、 鎖膨脹、方法內聯、空值檢查消除、類型檢測消除以及公共子表達式消除等。
解釋:
JIT編譯器屬于Java基礎中的比較有深度的題目了,回答出來算是一個亮點了。既然說到了JIT編譯器,我們來看下JIT對代碼優化使用到的逃逸分析技術吧。
逃逸分析:
逃逸分析的基本行為就是分析對象動態作用域,當一個對象在方法中被定義后,它可能被外部方法所引用,例如作為調用參數傳遞到其他地方中,稱為方法逃逸。JIT編譯器的優化包括如下:
同布省略:也就是鎖消除,當JIT編譯器判斷不會產生并發問題,那么會將同步synchronized去掉
標量替換
我們先來解釋下標量和聚合量的基本概念。
標量(Scalar)是指一個無法再分解成更小的數據的數據。Java中的原始數據類型就是標量。
聚合量(Aggregate)是還可以分解的數據。Java中的對象就是聚合量,因為他可以分解成其他聚合量和標量。
在JIT階段,如果經過逃逸分析,發現一個對象不會被外界訪問的話,那么經過JIT優化,就會把這個對象拆解成若干個其中包含的若干個成員變量來代替。這個過程就是標量替換。標量替換的好處就是對象可以不在堆內存進行分配,為棧上分配提供了良好的基礎。
那么逃逸分析技術存在哪些缺點呢?
技術不是特別成熟,分析的過程也很耗時,如果沒有一個對象是不逃逸的,那么就得不償失了。
三、Java中的值傳遞和引用傳遞
值傳遞和引用傳遞的解釋可以概括如下。
值傳遞,意味著傳遞了對象的一個副本,即使副本被改變,也不會影響源對象。
引用傳遞,意味著傳遞的并不是實際的對象,而是對象的引用。因此,外部對引用對象的改變會反映到所有的對象上。
我們先看一個值傳遞的例子:
public class Test {
public static void main(String[] args) {
int x=0;
change(x);
System.out.println(x);
}
static void change(int i){
i=7;
}
}
毫無疑問,上邊的代碼會輸出0。因為如果參數是基本數據類型,那么是屬于值傳遞的范疇,傳遞的其實是源對象的一個copy副本,不會影響源對象的值。
我們再來分析一個引用傳遞的例子:
public class Test {
public static void main(String[] args) {
StringBuffer x = new StringBuffer("Hello");
change(x);
System.out.println(x);
}
static void change(StringBuffer i) {
i.append(" world!");
}
}
通過運行程序,輸出為Hello world!接下來我們通過圖片來分析下程序執行過程種的內存變化吧。
由圖中我們可以看出x和i指向了同樣的內存地址,那么i.append操作將直接修改了內存地址里邊的值,所以當方法結束,局部變量i消失,先前變量x所指向的內存值已經發生了變化,所以輸出為Hello world!
接著,我們修改下change方法,代碼如下所示:
public class Test {
public static void main(String[] args) {
StringBuffer x = new StringBuffer("Hello");
change2(x);
System.out.println(x);
}
static void change2(StringBuffer i) {
i = new StringBuffer("hi");
i.append(" world!");
}
}
先給出答案,上邊Demo的輸出為Hello,我們依然來畫圖分析內存變化。
由圖中我們可以看出來,在函數change2中將引用變量i重新指向了堆內存中另一塊區域,下邊都是對另一塊區域進行修改,所以輸出是Hello。
最后,我們繼續升級該題目代碼如下:
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello ");
System.out.println("Before change, sb = " + sb);
changeData(sb);
System.out.println("After change, sb = " + sb);
}
public static void changeData(StringBuffer strBuf) {
StringBuffer sb2 = new StringBuffer("Hi,I am ");
strBuf = sb2;
sb2.append("World!");
}
}
輸出為:
Before change, sb = Hello
After change, sb = Hello
總結
以上是生活随笔為你收集整理的java的标量和聚合量_第5节:Java基础 - 必知必会(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: verilog中数组的定义_system
- 下一篇: 如何清洗海尔空调自动清洁功能?