jvm 記憶體區域劃分

u013378306發表於2017-07-06

JVM 深入筆記(1)記憶體區域是如何劃分的?

  • 作者:柳大 · Poechant
  • 電郵:zhongchao.ustc#gmail.com (#->@)
  • 部落格:blog.csdn.net/poechant
  • 日期:Feb. 21st 2012

一個超短的前言

JVM 是一個從事 Java 開發的軟體工程師的修煉之路上必然要翻閱的一座山。當你瞭解了 Java 的基本語言特性,當你熟悉了 Java SDK 中的常用 API,當你寫過一些或大或小的程式後,你就會有去了解 JVM 的需求出現。如果你現在沒有這種感覺,那麼可能此時去了解 JVM 並不是一個好的時機,因為你不會帶著問題去探索。

從本篇開始的系列博文,記錄本人的 JVM 深入學習總結,其中結合了本人自己的一些經驗,也參考了一些書籍和網路資源,然後根據自己的理解寫出這些博文。如有版權問題,請伊妹兒我 :)

謹以此係列博文分享給我的朋友們。

目錄

  1. JVM 簡史
  2. JVM 記憶體區域劃分
  3. 記憶體溢位場景模擬
  4. JVM 監控工具
  5. Garbage Collection

1 JVM 簡史

遮蔽不同的硬體平臺或作業系統上的環境差異,通過一個向上層提供統一程式設計介面來實現Java程式可移植性的軟體層,我們稱之為 Java 虛擬機器java Virtual Machine,簡稱 JVM)。

雖然 Java 的發展史可以追溯到 1991年4月由著名的 James Gosling 領導的 Green Project 計劃,但是 JDK 1.0 版本的正式釋出是在 1996年的1月23日,該版本提供的 JVM 是一個純解釋執行的 Sun Classic VM,不過是以外部載入的方式來使用的。而該版本的 JDK 所包含的主要技術除了 JVM 之外,就是 Applet 和 AWT。當然,此前在 Java 還叫做 Oak 的時候就已經有了一個完整的程式語言的外形,而 1995年5月23日,Oak 正式更名為 Java,並由 Sun 公司釋出了 Java 1.0 版本。

關於 Java 語言的背景,這裡就不多說了,主要還是介紹 JVM 的發展歷程。到 1998年發展出了 JDK 1.2,在該版本中 JVM 內建了 JIT (Just In Time) 編譯器,而 JDK 1.2 中也曾有過 Sun Classic VM、Hot Spot VM 和 Sun Exact VM 三種虛擬機器。其中 Hot Spot VM 和 Extract VM 都內建 JIT 編譯器。1997年,Sun 收購了開發 Hot Spot VM 的名為 Longview Technologies 的公司。也從此該虛擬機器改叫 Sun Hot Spot VM,當然那麼一個字首對於 Developers 來說是沒所謂的。從 JDK 1.3 開始,Sun Hot Spot VM 成為 Sun 公司釋出的 JDK 的預設 JVM。

目前活躍的商用 JVM 有 Sun Hot Spot、BEA JRockit 和 IBM J9。不過要說的是,JRockit 的主人 BEA 被 Oracle 收購了,而 Hot Spot 的主人被 Sun 公司在 2010 年也被 oracle 收購了。因此 Hot Spot 和 JRockit 都隸屬於 Oracle 公司。Oracle 曾稱將會將這個兩個 JVM 的優勢相融合,產生一款新的 JVM,屆時 Hot Spot 和 JRockit 也將進入歷史博物館了。JVM 的鼻祖 Sun Classic VM 早已被淘汰使用了,而 曾在 JDK 1.2 中靈光乍現過的 Sun Extract VM 也已經退出了歷史舞臺。另一個由 Apache 基金會主導的 Harmony 專案也有很大的影響,且間接由其催生的 Dalvik 虛擬機器,為 Google Android 的火爆發展做出了巨大的貢獻。在應用於手機、平板電腦、IVI、PDA 等裝置上的嵌入式 JVM 領域,除了 Dalvik,還有 KVM、CDC Hot Spot、CLDC Hot Spot 等 JVM 也較有影響力。

從本文開始的系列博文《JVM 原理與實戰》中所有實驗性程式的環境,都是 Mac OS X 10.7.3JDK 1.6.0 Update 29Oracle Hot Spot 20.4-b02

2 初識 JVM 記憶體區域劃分

大多數 JVM 將記憶體區域劃分為 Method Area(Non-Heap), HeapProgram Counter RegisterJava Method Stack,Native Method Stack 和 Direct Memomry(注意 Directory Memory 並不屬於 JVM 管理的記憶體區域)。前三者一般譯為:方法區、堆、程式計數器。但不同的資料和書籍上對於後三者的中文譯名不盡相同,這裡將它們分別譯作:Java 方法棧、原生方法棧和直接記憶體區。對於不同的 JVM,記憶體區域劃分可能會有所差異,比如 Hot Spot 就將 Java 方法棧和原生方法棧合二為一,我們可以同城為方法棧(Method Stack)。

首先我們熟悉一下一個一般性的 Java 程式的工作過程。一個 Java 源程式檔案,會被編譯為位元組碼檔案(以 class 為副檔名),然後告知 JVM 程式的執行入口,再被 JVM 通過位元組碼直譯器載入執行。那麼程式開始執行後,都是如何涉及到各記憶體區域的呢?

概括地說來,JVM 每遇到一個執行緒,就為其分配一個程式計數器、Java 方法棧和原生方法棧。當執行緒終止時,兩者所佔用的記憶體空間也會被釋放掉。棧中儲存的是棧幀,可以說每個棧幀對應一個“執行現場”。在每個“執行現場”中,如果出現了一個區域性物件,則它的例項資料被儲存在堆中,而類資料被儲存在方法區。

我們用上面這一小段文字就描述完了每個記憶體區域的基本功能。但是這還比較粗糙,下面分別介紹它們的儲存物件、生存期與空間管理策略。

2.1 程式計數器

  • 執行緒特性:私有
  • 儲存內容:位元組碼檔案指令地址(Java Methods),或 Undefined(Native Methods)
  • 生命週期:隨執行緒而生死
  • 空間策略:佔用記憶體很小

這個最簡單,就先撿它說吧。程式計數器,是執行緒私有(與執行緒共享相對)的,也就是說有 N 個執行緒,JVM 就會分配 N 個程式計數器。如果當前執行緒在執行一個 Java 方法,則程式計數器記錄著該執行緒所執行的位元組碼檔案中的指令地址。如果執行緒執行的是一個 Native 方法,則計數器值為 Undefined。

程式計數器的生存期多長呢?顯然程式計數器是伴隨執行緒生而生,伴隨執行緒死而死的。而它所佔用的記憶體空間也很小。

2.2 Java 方法棧與原生方法棧

Java 方法棧也是執行緒私有的,每個 Java 方法棧都是由一個個棧幀組成的,每個棧幀是一個方法執行期的基礎資料結構,它儲存著區域性變數表、運算元棧、動態連結、方法出口等資訊。當執行緒呼叫呼叫了一個 Java 方法時,一個棧幀就被壓入(push)到相應的 Java 方法棧。當執行緒從一個 Java 方法返回時,相應的 Java 方法棧就彈出(pop)一個棧幀。

其中要詳細介紹的是區域性變數表,它儲存者各種基本資料型別和物件引用(Object reference)。基本資料型別包括 boolean、byte、char、short、int、long、float、double。物件引用,本質就是一個地址(也可以說是一個“指標”),該地址是堆中的一個地址,通過這個地址可以找到相應的 Object(注意是“找到”,原因會在下面解釋)。而這個地址找到相應 Object 的方式有兩種。一種是該地址儲存著 Pointer to Object Instance Data 和 Pointer to Object Class Data,另一種是該地址儲存著 Object Instance Data,其中又包含有 Pointer to Object Class Data。如下兩圖所示。

Resize icon

圖1·控制程式碼方式的

Resize icon

圖2·直接方式

第一種方式,Java 方法棧中有 Handler Pool 和 Instance Pool。無論哪種方式,Object Class Data 都是儲存在方法區的,Object Instance Data 都是儲存在堆中的。

原生方法棧與 Java 方法棧相類似,這裡不再贅述。

2.3 堆

堆是在啟動虛擬機器的時候劃分出來的區域,其大小由引數或預設引數指定。當虛擬機器終止執行時,會釋放堆記憶體。一個 JVM 只有一個堆,它自然是執行緒共享的。堆中儲存的是所有的 Object Instant Data 以及陣列(不過隨著棧上分配技術、標量替換技術等優化手段的發展,物件也不一定都儲存在堆上了),這些 Instance 由垃圾管理器(Garbage Collector)管理,具體的演算法會在後面提到。

堆可以是由不連續的實體記憶體空間組成的,並且既可以固定大小,也可以設定為可擴充套件的(Scalable)。

2.4 方法區

通過(2)中 Java 方法棧的介紹,你已經知道了 Object Class Data 是儲存在方法區的。除此之外,常量、靜態變數、JIT 編譯後的程式碼也都在方法區。正因為方法區所儲存的資料與堆有一種類比關係,所以它還被稱為 Non-Heap。方法區也可以是記憶體不連續的區域組成的,並且可設定為固定大小,也可以設定為可擴充套件的,這點與堆一樣。

方法區內部有一個非常重要的區域,叫做執行時常量池(Runtime Constant Pool,簡稱 RCP)。在位元組碼檔案中有常量池(Constant Pool Table),用於儲存編譯器產生的字面量和符號引用。每個位元組碼檔案中的常量池在類被載入後,都會儲存到方法區中。值得注意的是,執行時產生的新常量也可以被放入常量池中,比如 String 類中的 intern() 方法產生的常量。

2.5 直接記憶體區

直接記憶體區並不是 JVM 管理的記憶體區域的一部分,而是其之外的。該區域也會在 Java 開發中使用到,並且存在導致記憶體溢位的隱患。如果你對 NIO 有所瞭解,可能會知道 NIO 是可以使用 Native Methods 來使用直接記憶體區的。

Reference

[1] 關於 Exact VM 的資料較少,我是在《深入理解 Java 虛擬機器》一書中首次瞭解到的。

JVM 深入筆記(1)記憶體區域是如何劃分的?

JVM 深入筆記(2)各記憶體區溢位場景模擬

-

轉載請註明來自“柳大的CSDN部落格”:blog.csdn.net/Poechant


相關文章