1. 程式人生 > >《Java程式設計思想》學習筆記1——面向物件和JVM基礎

《Java程式設計思想》學習筆記1——面向物件和JVM基礎

1.java中的4種訪問制許可權:

(1).public:最大訪問控制權限,對所有的類都可見。

(2).protect:同一包可見,不在同一個包的所有子類也可見。

(3).default:包訪問許可權,即同一個包中的類可以可見。預設不顯式指定訪問控制權限時就是default包訪問控制權限。

(4).private:最嚴格俄訪問控制權限,僅該類本身可見,對外一切類都不可以訪問(反射機制可以訪問)。

2.面向物件程式設計中兩種物件組合方式——is-a 和 has-a:

(1).is-a組合:一個類繼承具有相似功能的另一個類,根據需要在所繼承的類基礎上進行擴充套件。

優點:具有共同屬性和方法的類可以將共享資訊抽象到父類中,增強程式碼複用性,同時也是多型的基礎。

缺點:子類中擴充套件的部分對父類不可見,另外如果共性比較少的時候使用繼承會增加冗餘程式碼。

(2).has-a組合:has-a組合是在一個類中引用另一個類作為其成員變數。

優點:可擴充套件性和靈活性高。在物件組合關係中應優先考慮has-a組合關係。

缺點:具有共性的類之間看不到派生關係。

3.多型:

在面向物件程式設計中,子類中擁有和父類相同方法簽名的方法稱為子類方法覆蓋父類方法,當呼叫子類方法的某個操作時,不必明確知道子類的具體型別,只需要將子類型別看作是父類的引用呼叫其操作方法,在執行時,JVM會根據引用物件的具體子類型別而呼叫應該的方法,這就是多型。

多型的基礎是java面向物件程式設計的晚繫結機制。程式設計中有如下兩種繫結機制:

(1).早繫結:一般在非面向物件程式語言中使用,在程式編譯時即計算出具體呼叫方法體的記憶體地址。

(2).晚繫結:面向物件程式語言中經常使用,在程式編譯時無法計算出具體呼叫方法體的記憶體地址,只進行方法引數型別和返回值型別的校驗,在執行時才能確定具體要呼叫方法體的記憶體地址。

4.java單繼承的優點:

相比於C++的多繼承,java只支援類的單繼承,java中的所有類的共同基類是Object類,Object類java類樹的唯一根節點,這種單繼承有以下好處:

(1).單繼承可以確保所有的物件擁有某種共同的特性,這樣對於JVM虛擬機器對所有的類進行系統級的操作將提供方便,所有的java物件可以方便地在記憶體堆疊中建立,傳遞引數也變的更加方便簡單。

(2).java的單繼承使得實現垃圾回收器功能更加容易,因為可以確保JVM知道所有物件的型別資訊。

5.選擇容器物件兩個原則:

(1).容器所能提供不同的型別的介面和外部行為是否能夠滿足需求。

(2).不同容器針對不同的操作效率不同。

6.型別轉換:

Java中有兩種常見的型別轉換:向上型別轉換(upcast)和向下型別轉換(downcast):

(1).向上型別轉換(upcast):

向上型別轉換是將子類物件強制型別轉換為父類型別,經典用法是面向物件的多型特性。向上型別轉換時,子類物件的特性將不可見,只有子類從父類繼承的特性仍然保持可見,向上型別轉換時編譯器會自動檢查是否型別相容,通常是安全的。

(2).向下型別轉換:

向下型別轉換是將父類型別強制轉換為子類型別,轉換過後父類中不可見的子類特性又恢復可見性,向下型別轉換時,編譯器無法自動檢測是否型別相容,往往會產生型別轉換錯誤的執行時異常,通常不安全。

7.java中5個存放資料的地方:

(1).暫存器(Registers):位於CPU內部,是速度最快的儲存區,但是數量和容量有限。在java中不能直接操作暫存器。

(2).棧(Stack):棧位於通用隨機訪問儲存器 (General random-access memory,RAM,記憶體) 中,通過處理器的棧指標訪問,棧指標從棧頂向棧底分配記憶體,從棧底向棧頂釋放記憶體。棧是僅次於暫存器的速度第二快的儲存器,在java程式中,一般的8種基本型別資料和物件的引用通常存放在棧記憶體中,不通過new關鍵字的字串物件也是存放在棧的字串池中。棧的優勢是,存取速度比堆要快,僅次於暫存器,棧資料可以共享。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。

(3).堆(Heap):也是位於通用隨機訪問儲存器 (General random-access memory,RAM,記憶體) 中的共享記憶體池。Java的堆是一個執行時資料區,類的物件從中分配空間,凡是通過new關鍵字建立的物件都存放在堆記憶體中,它們不需要程式程式碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,因為它是在執行時動態分配記憶體的,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在執行時動態分配記憶體,存取速度較慢。

(4).常量儲存器(Constant storage):java中的常量是存放在系統內嵌的只讀儲存器中(read-only memory,ROM)的。

(5).非隨機儲存器(Non-RAM storage):對於流物件和持久化物件,通常存放在程式外的儲存器,如硬碟。

8.移位運算:

左移運算子<<:將位元位左移指定位數,右邊部分補0,左移一位相當於乘2。

右移運算子>>:將位元位右移指定位數,如果是正數,左邊第一位(符號位)補0,其餘位補0,如果是負數,左邊第一位補1,其餘位補0。右移一位相當於除2。

無符號右移運算子>>>:將位元位右移指定位數,不論是正數或者負數,左邊移除位統統補0。

9.java中,比int型別小的原始型別(char、byte、short)進行數學運算或者位運算時,資料型別首先轉換成int型別,然後進行相應的運算。

10.方法過載(overloading):方法同名,引數列表不同稱為方法過載,注意方法的返回值型別不同不能作為方法過載。

11.java中的解構函式:

Java中沒有像C/C++的解構函式,用來銷燬不用的物件是否記憶體空間,只有以下三個方法用於通知垃圾回收器回收物件。

(1).finalize( )只是通知JVM的垃圾收集器當前的物件不再使用可以被回收了,但是垃圾回收器根據記憶體使用狀況來決定是否回收。

finalize()最有用的地方是在JNI呼叫本地方法時(C/C++方法),呼叫本地方法的解構函式消耗物件釋放函式。

(2). System.gc()是強制析構,顯式通知垃圾回收器釋放記憶體,但是垃圾回收器也不一定會立即執行,垃圾回收器根據當前記憶體使用狀況和物件的生命週期自行決定是否回收。

(3).RunTime.getRunTime().gc()System.gc()類似。

注意:這三個函式都不能保證垃圾回收器立即執行,推薦不要頻繁使用。

12.垃圾回收器原理:

(1).引用計數(ReferenceCounting)垃圾回收演算法:

一種簡單但是速度較慢的垃圾回收演算法,每個物件擁有一個引用計數器(Reference Counter),當每次引用附加到這個物件時,物件的引用計數器加1。當每次引用超出作用範圍或者被設定為null時,物件的引用計數器減1。垃圾回收器遍歷整個物件列表,當發現一個物件的引用計數器為0時,將該物件移出記憶體釋放。

引用計數演算法的缺點是,當物件環狀相互引用時,物件的引用計數器總不為0,要想回收這些物件需要額外的處理。

引用計數演算法只是用來解釋垃圾回收器的工作原理,沒有JVM使用它實現垃圾回收器。

引用計數的改進演算法:

任何存活的物件必須被在靜態儲存區或者棧(Stack)中的引用所引用,因此當遍歷全部靜態儲存區或棧中的引用時,即可以確定所有存活的物件。每當遍歷一個引用時,檢查該引用所指向的物件,同時檢查該物件上的所有引用,沒有引用指向的物件和相互自引用的物件將被垃圾回收器回收。

(2).暫停複製(stop-and-copy)演算法:

垃圾回收器的收集機制基於:任何一個存活的物件必須要被一個儲存在棧或者靜態儲存區的引用所引用。

暫停複製的演算法是:程式在執行過程中首先暫停執行,把每個存活的物件從一個堆複製到另一個堆中,已經不再被使用的物件被回收而不再複製。

暫停複製演算法有兩個問題:

a.必須要同時維護分離的兩個堆,需要程式執行所需兩倍的記憶體空間。JVM的解決辦法是在記憶體塊中分配堆空間,複製時簡單地從一個記憶體塊複製到另一個記憶體塊。

b.第二個問題是複製過程的本身處理,當程式執行穩定以後,只會產生很少的垃圾物件需要回收,如果垃圾回收器還是頻繁地複製存活物件是非常低效能的。JVM的解決方法是使用一種新的垃圾回收演算法——標記清除(mark-and-sweep)。

一般來說標記清除演算法在正常的使用場景中速度比較慢,但是當程式只產生很少的垃圾物件需要回收時,該演算法就非常的高效。

(3).標記清除(mark-and-sweep)演算法:

和暫停複製的邏輯類似,標記清除演算法從棧和靜態儲存區開始追蹤所有引用尋找存活的物件,當每次找到一個存活的物件時,物件被設定一個標記並且不被回收,當標記過程完成後,清除不用的死物件,釋放記憶體空間。

標記清除演算法不需要複製物件,所有的標記和清除工作在一個記憶體堆中完成。

注意:SUN的文件中說JVM的垃圾回收器是一個後臺執行的低優先順序程序,但是在早期版本的JVM中並不是這樣實現的,當記憶體不夠用時,垃圾回收器先暫停程式執行,然後進行垃圾回收。

(4).分代複製(generation-copy)演算法:

一種對暫停複製演算法的改進,JVM分配記憶體是按塊分配的,當建立一個大物件時,需要佔用一塊記憶體空間,嚴格的暫停複製演算法在釋放老記憶體堆之前要求把每個存活的物件從源堆拷貝到新堆,這樣做非常的消耗記憶體。

通過記憶體堆,垃圾回收器可以將物件拷貝到回收物件的記憶體堆中,每個記憶體塊擁有一個世代計數(generation count)用於標記物件是否存活。每個記憶體塊通過物件被引用獲得世代計數,一般情況下只有當最老的記憶體塊被回收時才會建立新的記憶體塊,這主要用於處理大量的短存活週期臨時物件回收問題。一次完整的清理過程中,記憶體塊中的大物件不會被複制,只是根據引用重新獲得世代計數。

JVM監控垃圾回收器的效率,當發現所有的物件都是長時間存活時,JVM將垃圾回收器的收集演算法調整為標記清除,當記憶體堆變得零散碎片時,JVM又重新將垃圾回收器的演算法切換會暫停複製,這就是JVM的自適應分代暫停複製標記清除垃圾回收演算法的思想。

13.java即時編譯技術(JIT):

Java的JIT是just-in-timecomplier技術,JIT技術是java程式碼部分地或全部轉換成本地機器碼程式,不再需要JVM解釋,執行速度更快。

當一個”.class”的類檔案被找到時,類檔案的位元組碼被調入記憶體中,這時JIT編譯器編譯位元組碼程式碼。

JIT有兩個不足:

(1).JIT編譯轉換需要花費一些時間,這些時間貫穿於程式的整個生命週期。

(2).JIT增加了可執行程式碼的size,相比於壓縮的位元組碼,JIT程式碼擴充套件了程式碼的size,這有可能引起記憶體分頁,進而降低程式執行速度。

對JIT不足的一種改進技術是延遲評估(lazy evaluation):其基本原理是位元組碼並不立即進行JIT編譯除非必要,在最近的JDK中採用了一種類似延遲JIT的HotSpot方法對每次執行的程式碼進行優化,程式碼執行次數越多,速度越快。

14.非內部類的訪問控制權限只能是預設的包訪問許可權或者是public的,不能是protected和private的。內部類的訪問控制權限可以是protected和private。

15.Java中的高精度數值型別:

BigInteger和BigDecimal是java中的高精度數值型別,由於它們是用於包裝java的基本資料型別,因此這兩個高精度數值型別沒有對應的原始型別。

(1).BigInteger支援任意精度的整數,即使用BigInteger可以表示任意長度的整數值而在運算中不會因為範圍溢位丟失資訊。

(2).BigDecimal支援任意精度的固定位數浮點數,可以用來精確計算貨幣等數值。

普通的float和double型浮點數因為受到小數點位數限制,在運算時不能準確比較,只能以誤差範圍確定是否相等,而BigDecimal就可以支援固定位數的浮點數並進行精確計算。

16.Java只處理public和protected訪問控制權限成員的文件註釋,private和預設的包訪問控制權限成員的文件註釋將被忽略。

17.Java中賦值運算:

Java中賦值運算是把賦值運算子”=”右邊的值簡稱右值拷貝到賦值運算子左邊的變數,如a=b,即把b代表的變數或常量值複製給變數a,切記a只能是變數,不能說常量值。

(1).原始型別賦值運算:

Java中8種原始資料型別賦值運算是將賦值運算子右邊的值拷貝到賦值運算子左邊的變數中。

原始型別賦值運算後,無論改變賦值運算子那一邊的值,都不會影響賦值運算子另一邊的值。

(2).引用型別的賦值運算:

Java中除了8中原始資料型別外,所有的資料型別都是物件型別,物件型別的賦值運算是操作引用,如a=b,把b引用賦值給a引用,即原本b引用指向的物件現在由a和b引用同時指向。

引用賦值運算子又叫別名運算子,即它相當於給引用物件取了一個別名,其實引用的還是同一個物件。

引用型別的賦值運算,如果賦值運算子任意一邊的引用改變了被引用物件的值,賦值運算子另一邊的引用也會受影響,因為兩個引用指向的是同一個被引用的物件。