Java虛擬機(jī)

1. Java內(nèi)存區(qū)域

答:


Java虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)
(1) Java堆(線程共享)
  • 是JVM所管理的內(nèi)存中最大的一塊。
  • 在虛擬機(jī)啟動時創(chuàng)建。
  • 唯一目的就是存放對象,幾乎所有的類實例和數(shù)組都要在堆上分配。
  • Java 堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱為“GC 堆”。
  • Java 堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可。
  • 在實現(xiàn)時,既可以實現(xiàn)成固定大小的,也可以是可擴(kuò)展的;當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實現(xiàn)的(通過 -Xmx 和 -Xms 控制)。
  • 如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴(kuò)展時,將會拋出 OutOfMemoryError 異常。
(2) 方法區(qū)(線程共享)
  • 用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
  • 除了和 Java 堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不實現(xiàn)垃圾回收。
  • 相對而言,垃圾回收行為在這個區(qū)域是比較少出現(xiàn)的;這區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載。
  • 當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出 OutOfMemoryError 異常。
運(yùn)行時常量池
  • 是方法區(qū)的一部分。
  • Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時常量池中存放。
  • 對于運(yùn)行時常量池,Java 虛擬機(jī)規(guī)范沒有做任何細(xì)節(jié)的要求。不過,一般來說,除了保存 Class 文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運(yùn)行時常量池中。
  • 運(yùn)行時常量池相對于 Class 文件常量池的另外一個重要特征是具備動態(tài)性,Java 語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入 Class 文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是 String 類的 intern() 方法。
  • 當(dāng)運(yùn)行時常量池?zé)o法再申請到內(nèi)存時會拋出 OutOfMemoryError 異常。
(3) Java虛擬機(jī)棧(線程私有)
  • 生命周期與線程相同。
  • 描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行時都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中入棧到出棧的過程。
  • 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、char、byte、short、int、long、double)、對象引用(reference 類型)和 returnAddress 類型(指向了一條字節(jié)碼指令的地址)。
  • 如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出 StackOverflowError 異常。
  • 如果虛擬機(jī)??梢詣討B(tài)擴(kuò)展(當(dāng)前大部分的 Java 虛擬機(jī)都可動態(tài)擴(kuò)展,只不過 Java 虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),如果擴(kuò)展時無法申請到足夠的內(nèi)存,就會拋出 OutOfMemoryError 異常。
(4) 本地方法棧(線程私有)
  • 與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,區(qū)別在于,虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 native 方法服務(wù)。
  • 與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會拋出 StackOverflowError 和 OutOfMemoryError 異常。
(5) 程序計數(shù)器(線程私有)
  • 一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。
  • 在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。
  • 為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計數(shù)器,各條線程之間計數(shù)器互不影響,獨(dú)立存儲,這類內(nèi)存區(qū)域我們稱之為“線程私有”的內(nèi)存。
  • 此內(nèi)存區(qū)域是唯一一個在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域。

2. Java內(nèi)存模型

Java 內(nèi)存模型(Java Memory Model,JMM),用來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓 Java 程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果。

主要目標(biāo):定義程序中各個變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。
注意:此處的變量與Java編程中所說的變量有所區(qū)別,它包括了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但不包括局部變量與方法參數(shù),因為后者是線程私有的,不會被共享,自然就不會存在競爭問題。

主內(nèi)存與工作內(nèi)存

Java 內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存(Main Memory)中。
每條線程還有自己的工作內(nèi)存(Working Memory),線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。
不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成。
線程、工作內(nèi)存、主內(nèi)存三者的交互關(guān)系如下圖所示:

線程、工作內(nèi)存和主內(nèi)存之間的交互關(guān)系

注意:
這里所講的主內(nèi)存、工作內(nèi)存與前面所講的 Java 內(nèi)存區(qū)域中的 Java 堆、棧、方法區(qū)等并不是同一個層次的內(nèi)存劃分,兩者基本上是沒有關(guān)系的。

  • 如果兩者一定要勉強(qiáng)對應(yīng)起來的話,那從變量、主內(nèi)存、工作內(nèi)存的定義來看,主內(nèi)存主要對應(yīng)于 Java 堆中的類實例數(shù)據(jù)部分,而工作內(nèi)存則對應(yīng)于虛擬機(jī)棧中的部分區(qū)域。
  • 從更低層次上說,主內(nèi)存就直接對應(yīng)于物理硬件的內(nèi)存,而為了獲取更好的運(yùn)行速度,虛擬機(jī)可能會讓工作內(nèi)存優(yōu)先存儲于寄存器和高速緩存中,因為程序運(yùn)行時主要訪問讀寫的是工作內(nèi)存。
內(nèi)存間交互操作

關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存之類的實現(xiàn)細(xì)節(jié),Java 內(nèi)存模型中定義了以下8種操作來完成,虛擬機(jī)實現(xiàn)時必須保證下面提及的每一種操作都是原子的、不可再分的。

  • lock(鎖定):作用于主內(nèi)存的變量,它把一個變量標(biāo)識為一條線程獨(dú)占的狀態(tài)。
  • unlock(解鎖):作用于主內(nèi)存的變量,它把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
  • read(讀?。?/strong>:作用于主內(nèi)存的變量,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的 load 操作使用。
  • load(載入):作用于工作內(nèi)存的變量,它把 read 操作從主內(nèi)存中得到的變量的值放入工作內(nèi)存的變量副本中。
  • use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個需要使用到變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
  • store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存中,以便隨后的 write 操作使用。
  • write(寫入):作用于主內(nèi)存的變量,它把 store 操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

Java 內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時必須滿足如下規(guī)則:

  • 不允許 read 和 load、store和 write 操作之一單獨(dú)出現(xiàn),即不允許一個變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況出現(xiàn)。
  • 不允許一個線程丟棄它的最近的 assign 操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步

3. 類加載機(jī)制

定義

虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的 Java 類型,這就是虛擬機(jī)的類加載機(jī)制

4. 如何判斷一個對象是否存活?

在堆里面存放著 Java 世界中幾乎所有的類實例,垃圾收集器在對堆進(jìn)行回收前,第一件事情就是要確定這些對象中哪些還“存活”著,哪些已經(jīng)“死去”(即不可能再被任何途徑使用的對象)。

(1) 引用計數(shù)算法

5. 垃圾收集算法

(1) 標(biāo)記 - 清除算法
(2) 復(fù)制算法
(3) 標(biāo)記 - 整理算法
(4) 分代收集算法

6. 垃圾收集器

(1) Serial 收集器
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容