首頁 > 軟體

Java虛擬機器

2020-09-22 15:00:42

虛擬機器記憶體劃分

  1. 程式計數器

計數器記錄了正在執行的虛擬機器位元組碼指令的地址,如果執行的是native方法,計數器值為空

  1. Java虛擬機器棧

虛擬機器棧描述的是Java方法執行的記憶體模型,每一個方法從呼叫到執行完成的過程,對應著一個棧幀在虛擬機器中入棧到出棧的過程虛擬機器棧不可以動態擴展時,如果執行緒請求的棧深度大於虛擬機器所允許的深度,會拋出StackOverflowError異常。虛擬機器棧動態擴展時,如果擴展時無法申請到足夠的記憶體,就會拋出OutOfMemoryError異常。

  1. 本地方法棧

與虛擬機器棧相似,不過對應的是native方法服務,同樣會拋出上述異常。

  1. Java堆

存放物件例項,邏輯上是連續的,物理上 不一定連續,可通過-Xmx和-Xms擴展

  1. 方法區

用於儲存已被虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料。

  1. 運行時常量池

用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類載入後進入方法去的運行時常量池中存放,以及string常量,常用於intern方法(當呼叫 intern 方法時,如果池已經包含一個等於此 String 物件的字元串(用 equals(Object) 方法確定),則返回池中的字元串。否則,將此 String 物件新增到池中,並返回此 String 物件的引用。)


物件的創建過程(p45)

虛擬機器遇到一個new指令後,首先檢查該指令參數能否在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已被載入,解析初始化,如果沒有則執行類載入過程。

物件的訪問定位(Java程式需要通過棧上的reference資料來操作堆上的具體物件)
  1. 控制代碼方式

Java堆中會劃分出一塊記憶體來作為控制代碼池,reference中儲存了物件的控制代碼地址,而控制代碼包含了物件的具體地址。

  1. 直接指針訪問

Reference中儲存了物件的的真實地址

優劣:物件被移動只會改變控制代碼鍾到例項資料指針,reference不需要修改,直接指針速度快。


垃圾回收演算法

  1. 引用計數演算法

物件儲存一個引用計數器,每當有一個地方引用它時,計數器值加一,引用是失效時則減一,為0則不在被使用,缺點是難以解決迴圈引用的問題。

  1. 可達性分析演算法

通過稱為“GC root”的物件為起始點,從這些起始點向下搜尋,當一個物件到GC root 沒有任何引用鏈相連,則這個物件是不可達的,在列舉根節點分析時保證物件引用關係不變化,所以會造成停頓。

GC root物件:

a. 虛擬機器棧中引用的物件

b. 方法區中靜態變數屬性引用的物件

c. 方法區中常量引用的物件

d. 本地方法棧中引用的物件

  1. 標記清除演算法

標記完後續統一回收,但標記清除效率不高,會產生大量不連續的記憶體碎片,該演算法是GC的基礎。

  1. 複製演算法

Java中將堆分為新生代和老年代,新生代又分為Eden和兩個servivor空間,每次將存活物件複製到另一個servivor中,如果servivor空間不足,則由老年代進行分配擔保,儲存這些物件。

  1. 標記整理演算法

該演算法工作在老年代,在標記後將存活物件向一端移動,清理另一邊的記憶體,以應對物件存活率較高的情況。

  1. 分代收集演算法

即將堆分為新生代和老年代,再在不同代的堆運行不同的演算法,新生代採用複製演算法,老年代使用標記清除或者標記整理演算法。


物件回收過程

首先進行可達性分析,這裡會造成GC停頓,停頓指的是執行緒執行到安全點停頓,如果物件不可達,則開始第一次標記篩選,篩選條件是是否有必要執行finalize方法,如果覆蓋了finalize方法,且未被執行過,則將物件放置在F-Queue佇列中,稍後GC會對該佇列進行第二次標記並執行finalize方法,在finalize方法中沒有將自身引用賦值給其他變數,則回收。

垃圾收集器

Serial收集器

單執行緒收集器,工作在新生代,進行垃圾收集時,必須暫停其他執行緒,在新生代採取複製演算法

ParNew收集器

Serial收集器的多執行緒版本,該收集器多個GC執行緒並行執行,但同樣需要暫停使用者執行緒,能與CMS收集器配合各工作,因CMS只能和ParNew或者Serial收集器配合工作,所以該收集器時Server模式首選收集器。

Parallel Scavenge收集器

新生代收集器,採用複製演算法,該收集器的特點是吞吐量可控制,即CPU使用者執行緒運行的時間與總時間的比值

Serial Old收集器

Serial收集器的老年代版本,採用標記整理演算法,主要給client模式下的虛擬機器使用,單執行緒。

Parallel Old收集器

Parallel Scavenge收集器的老年代版本,採用標記整理演算法,多執行緒

CMS收集器

工作在老年代,以獲取最短回收停頓時間為目標的收集器,響應速度快,使用者體驗好。基於標記清除演算法,分為四步:初始標記僅標記下GC Root直接關聯的物件,需要暫停使用者執行緒;併發標記不用暫停使用者執行緒;重新標記修正併發標記時標記變動的那部分物件,需要暫停使用者執行緒;最後時併發清除,不需要暫停使用者執行緒。

G1收集器

地表最強收集器,堆不再劃分成新生代和老年代,而是一塊塊大小相等的記憶體區域,G1會根據小堆的垃圾佔比進行有優先順序的區域回收方式。G1收集器分為四個步驟;初始標記,併發標記,最終標記,篩選回收。初始標記僅標記GC Roots直接關聯的物件,需停頓使用者執行緒;併發標記與使用者執行緒併發執行,對堆中物件進行可達性分析,找出存活物件;最終標記修正並行標記階段標記產生變化的記錄,可並行執行,但需停頓使用者執行緒;篩選回收則清理不可用物件,可與使用者執行緒併發執行,但一般停頓使用者執行緒效率更高


Minor GC和Full GC的區別

Minor GC發生在新生代,比較頻繁,Full GC發生在老年代,速度慢。堆中記憶體不足會觸發GC,要避免老年代的GC。

物件進入老年代的時機

大物件直接進入老年代,所以大物件存活時間又短的容易觸發Full GC;Minor GC時,survivor空間不足,物件因分配擔保進入老年代;物件儲存有年齡計數器,每進行一次Minor GC,年齡加一,到了閾值會進入老年代;動態年齡判定,在survivor空間中相同年齡所有物件大於survivor的一半,該年齡以上的物件進入老年代。

類載入的時機
  1. 使用new關鍵字例項化時,讀取或設定一個類的靜態欄位時(對於final修飾的類常量,在呼叫時並不會觸發被呼叫類的初始化,因為該常量已被編譯到呼叫類的常量池位元組碼中),呼叫一個類的靜態方法時。

  2. 使用java.lang.reflect包的方法對類進行反射呼叫時,類未被初始化。

  3. 初始化一個類時,父類沒有被初始化,需先初始化父類(介面不要求父介面已初始化,除非用到了父類介面)。

  4. 虛擬機器啟動時初始化mian方法主類。

  5. 使用動態語言支援時。

除此之外,其他任何情況都不會觸發類的初始化,如通過子類呼叫父類的靜態欄位,不會初始化子類。


類載入過程
  1. 載入

將類載入進方法區,生成代表該類的class物件,作為方法區該類的各種資料入口。

  1. 驗證

驗證位元組流是否符合class檔案規範,確保class檔案的位元組流符合當前虛擬機器規範。

  1. 準備

為類變數分配記憶體並設定類變數初始值,除final類型的變數,其餘都賦值為零或null或false。

  1. 解析

將符號引用替換為直接引用

  1. 初始化

執行類中Java程式碼,將初始值替換為真實賦值。

注意:初始化時程式碼執行是從上往下執行的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變數,定義在它之後的類變數,只能賦值(在準備階段已經分配了記憶體),不能訪問(還沒有賦真值,訪問的值是無效的,這就不安全了)。

子類和父類初始化順序:

1,在類載入的時候執行父類的static程式碼塊,並且只執行一次(因為類只加載一次);

2,執行子類的static程式碼塊,並且只執行一次(因為類只加載一次);

3,執行父類的類成員初始化,並且是從上往下按出現順序執行;

4,執行父類的建構函式;

5,執行子類的類成員初始化,並且是從上往下按出現順序執行。

6,執行子類的建構函式。


雙親委派模型

Java中類載入器可分為三類:

  1. 啟動類載入器

使用c++語言實現,負責載入lib目錄中的類庫,無法被Java程式直接引用。

  1. 擴展類載入器

使用Java實現,負責載入libext目錄中的類庫,開發之可以直接使用。

  1. 應用程式類載入器

由ClassLoader實現,負責載入使用者類路徑(classpath)上指定的類庫,開發者可以直接使用。

雙親委派模型的工作過程:如果一個類收到了類載入的請求,會首先把這個請求委派給父類載入器(使用組合關係來複用父載入器的程式碼),因此所有的載入請求都委派到頂層的啟動類載入器,只有父類載入器無法完成這個載入請求,子載入器才會嘗試自己去載入,這樣保證了object類在程式中都是同一個類,保證了程式的穩定。


IT145.com E-mail:sddin#qq.com