第一部分 走進Java
1.1 概述
1. 略,大致內容就是吹Java,不過用的java基本上都覺得java是世界上最好的語言吧!
1.2 java技術體系
1. 從廣義上將,Kotlin、Clojure、JRuby、Groovy等執行於Java虛擬機器上的程式語言及其相關的程式都屬於java技術體系中的一員。如果僅從傳統意義上來看,JCP官方所定義的java技術體系包括了一下幾個組成部分:
1. java程式設計語言
2. 各種硬體平臺上的Java虛擬機器實現
3. .Class檔案格式
4. .Java類庫API
5. 來自商業機構和開源社羣的第三方java類庫
2. 我們把java程式設計語言、Java虛擬機器、Java類庫這三個部分稱爲jdk(java development kit),jdk是用於支援Java程式開發的最小環境。
3. 把Java類庫api中的Java se api子集和Java虛擬機器這兩部分統稱爲jre(java runtime environment),jre是支援Java程式執行的標準環境。
4. 如果按照技術所服務的領域來劃分的話,Java技術體系可以分爲一下四條主要的產品線:
1. java card:支援java小程式(Applets)執行在小記憶體裝置上的平臺。
2. java me
3. java se
4. java ee
1.3 java發展史
1.4 java虛擬機器家族
1.5 展望Java技術的未來
1.6 實戰:自己編譯jdk
1.7 本章小結
第二部分 自動記憶體管理
第2章 java記憶體區域與記憶體溢位異常
1. Java與C++之間有一堵由記憶體動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裏面的人卻想出來。
2. 對於從事C、C++程式開發的開發人員來說,在記憶體管理領域,他們即是擁有最高權力的「皇帝」,又是從事最基礎工作的勞動人民--即擁有每一個物件的所有權,又擔負着每個一物件生命從開始到終結的維護責任。
2.2 執行時數據區域
1. Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分爲若幹個不同的數據區域。這些區域有各自的用途,以及建立和銷燬時間,有的區域隨着虛擬機器進程的啓動而一直存在,有些區域則是依賴使用者執行緒的啓動和結束而建立和銷燬。Java虛擬機器所管理的記憶體將會包括以下幾個執行時數據區域
1. 由所有執行緒共用的數據區:方法區(Method Area)、堆(Heap)、執行引擎、本地庫介面
2. 執行緒隔離的數據區:虛擬機器棧(VM Stack)、本地方法棧(Native Method Stack)、程式計數器(Program Counter Register)、本地方法庫。
2.2.1 程式計數器
1. 程式計數器(Program Counter Register)是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。在Java虛擬機器的概念模型裡,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,它是程式控制流的指示器,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。
2. 由於Java虛擬機器的多執行緒是通過執行緒輪流切換、分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)都只會執行一條執行緒中的指令。因此,爲了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間計數器互補影響,獨立儲存,我們稱這類記憶體區域爲「執行緒私有」的記憶體。
3. **如果執行緒正在執行的是Java方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是本地(Native)方法,這個計數器值則應爲空(Undefined)。此記憶體區域是唯一一個在《java虛擬機器規範》中沒有規定任何OutMemoryError情況的區域。**
2.2.2 Java虛擬機器棧
1. 與程式計數器一樣,Java虛擬機器棧(Java Virtual Machine Stack)也是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的執行緒記憶體模型:每個方法被執行的時候,Java虛擬機器都會同步建立一個棧幀(Stack Frame)用於儲存區域性變數表、運算元棧、動態連線、方法出口等資訊。每一個方法被呼叫直至執行完畢的過程,就對應着一個棧幀在虛擬機器棧中從入棧到出棧的過程。
2. 經常有人把Java記憶體區域籠統地劃分爲堆記憶體(Heap)和棧記憶體(Stack),這種劃分方式直接繼承自傳統地C、C++程式的記憶體佈局結構,在Java語言裡就顯得有些粗糙了,實際的記憶體區域劃分要比這更復雜。不過這種劃分方式的流行也間接說明了程式設計師最關注的、與物件記憶體分配關係最密切的區域是「堆」和「棧」兩塊。其中,「堆」在稍後會專門講述,而「棧」通常就是指這裏講的虛擬機器棧,或者更多的情況下只是指虛擬機器棧中區域性變數表部分。
3. 區域性變數表存放了編譯期可知的各種Java虛擬機器基本數據型別(boolean、byte、char、short、int、float、long、double)、物件參照(reference型別,它並不等同於物件本身,可能是一個指向物件起始地址的參照指針,也可能是指向一個代表物件的控制代碼或者其他與此物件相關的位置)和returnAddress型別(指向了一條位元組碼指令的地址)。
4. 這些數據型別在區域性變數表中的儲存空間以區域性變數槽(Slot)來表示,其中64位元長度的long和double型別的數據會佔用兩個變數槽,其餘的數據型別只佔用一個。區域性變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變數表大小。
5. 在《Java虛擬機器規範》中,對這個記憶體區域規定了兩類異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將拋出StackOverflowError異常;如果Java虛擬機器棧容量可以動態擴充套件,當棧擴充套件時無法申請到足夠的記憶體會拋出OutOfMemoryError異常。
2.2.3 本地方法棧
1. 本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別只是虛擬機器棧爲虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則是爲虛擬機器使用到的本地(Native)方法服務。
2. 《Java虛擬機器規範》對本地方法棧中方法使用的語言、使用方式與數據庫結構並沒有任何強制規定,因此具體的虛擬機器可以根據需要自由實現它,甚至有的Java虛擬機器(譬如Hot-spot虛擬機器)直接就把本地方法棧和虛擬機器合二爲一。與虛擬機器棧一樣,本地方法棧也會在棧深度溢位或者棧擴充套件失敗時分別拋出StackOverflowError和OutOfMemoryError異常。
2.2.4 Java堆
1. 對於Java應用程式來說,Java堆(Java Heap)是虛擬機器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共用的一塊記憶體區域,在虛擬機器啓動時建立。此記憶體區域的唯一目的就是存放物件範例,Java世界裏"幾乎"所有的物件範例都在這裏分配記憶體。在《Java虛擬機器規範》中對Java堆的描述是:"所有的物件範例以及陣列都應當在堆上分配",用幾乎是因爲Java物件範例都分配在堆上也漸漸變得不是那麼絕對了。
2. Java堆是垃圾收集器管理的記憶體區域,因此一些資料中它也被稱作"GC堆(Garbage Collected Heap)"。從回收記憶體的角度看,由於現代垃圾收集器大部分都是基於分代收集理論設計的。
3. 如果從分配記憶體的角度看,所有執行緒共用的Java堆中可以劃分出多個執行緒私有的分配緩衝區(Thread Local Allocation Buffer TLAB),以提升物件分配時的效率。將Java堆細分的目的只是爲了更好的回收記憶體,或者更快地分配記憶體。
4. 根據《Java虛擬機器規範》地規定,Java堆可以處於物理上不連續地記憶體空間中,但在邏輯上它應該被視爲連續的,這點就像我們用磁碟空間去儲存檔案一樣,並不要求每個檔案都連續存放。但對於大物件(典型的如陣列物件),多數虛擬機器實現出於實現簡單、儲存高效地考慮,很可能會要求連續地記憶體空間。
5. Java堆既可以被實現成固定大小的,也可以是可延伸的,不過當前主流的Java虛擬機器都是按照可延伸來實現的(通過參數-Xmx和-Xms設定)。如果在Java堆中沒有記憶體完成實力分配,並且堆也無法再擴充套件時,Java虛擬機器將會拋出OutOfMemoryError異常。
2.2.5 方法區
2.2.6 執行時常數池
1. 執行時常數池(Runtime Constant Pool)是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常數池表(Constant Pool Table),用於存放編譯期生成的各種字面量和符號參照,這部分內容將在類載入後存放到方法區的執行時常數池中。
2. Java虛擬機器對於Class檔案每一部分(自然也包括常數池)的格式都有嚴格規定,如每一個位元組用於儲存那種數據都必須符合規範上的要求纔會被虛擬機器認可、載入和執行,但對於執行時常數池,《Java虛擬機器規範》並沒有做任何細節的要求,不過一般來說,除了儲存Class檔案中描述的符號參照外,還會把由符號參照翻譯出來的直接參照也儲存到執行時常數池中。
3. 執行時常數池相對於Class檔案常數池的另外一個重要特徵是具備動態性,Java語言並不要求常數一定只有編譯期才能 纔能產生,也就是說,並非預置入Class檔案中常數池的內容才能 纔能進入方法區執行時常數池,執行期間也可以將新的常數放入池中,這種特性被開發人員利用的比較多的便是String類的intern()方法。
4. 既然執行時常數池是方法區的一部分,自然受到方法區記憶體的限制,當常數池無法再申請到記憶體時會拋出OutOfMemoryError異常。