JVM內存區域劃分(JDK6/7/8中的變化)

jopen 8年前發布 | 40K 次閱讀 Java開發

前言

Java程序的運行是通過Java虛擬機來實現的。通過類加載器將class字節碼文件加載進JVM,然后根據預定的規則執行。Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分為若干個不同的數據區域。這些內存區域被統一叫做運行時數據區。Java運行時數據區大致可以劃分為5個部分。如下圖所示。在這里要特別指出,我們現在說的JVM內存劃分是概念模型。具體到每個JVM的具體實現可能會有所不同。具體JVM的實現我只會提到HotSpot虛擬機的實現細節。

這里寫圖片描述

程序計數器

程序計數器是一塊較小的內存空間,它可以看成是當前線程所執行的字節碼的行號指示器。程序計數器記錄線程當前要執行的下一條字節碼指令的地址。由于Java是多線程的,所以為了多線程之間的切換與恢復,每一個線程都需要單獨的程序計數器,各線程之間互不影響。這類內存區域被稱為“線程私有”的內存區域。
由于程序計數器只存儲一個字節碼指令地址,故此內存區域沒有規定任何OutOfMemoryError情況。

虛擬機棧

Java虛擬機棧也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行時都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。
一個棧幀就代表了一個方法執行的內存模型,虛擬機棧中存儲的就是當前執行的所有方法的棧幀(包括正在執行的和等待執行的)。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機中入棧到出棧的過程。我們平時所說的“局部變量存儲在棧中”就是指方法中的局部變量存儲在代表該方法的棧幀的局部變量表中。而方法的執行正是從局部變量表中獲取數據,放至操作數棧上,然后在操作數棧上進行運算,再將運算結果放入局部變量表中,最后將操作數棧頂的數據返回給方法的調用者的過程。(關于棧幀和基于棧的方法執行,我會在之后寫兩篇文章專門介紹。敬請期待?)
虛擬機棧可能出現兩種異常:由線程請求的棧深度過大超出虛擬機所允許的深度而引起的StackOverflowError異常;以及由虛擬機棧無法提供足夠的內存而引起的OutOfMemoryError異常。

本地方法棧

本地方法棧與虛擬機棧類似,他們的區別在于:本地方法棧用于執行本地方法(Native方法);虛擬機棧用于執行普通的Java方法。在HotSpot虛擬機中,就將本地方法棧與虛擬機棧做在了一起。
本地方法棧可能拋出的異常同虛擬機棧一樣。

Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例:所有的對象實例以及數組都要在堆上分配(The heap is the runtime data area from which memory for all class instances and arrays is allocated)。但Class對象比較特殊,它雖然是對象,但是存放在方法區里。在下面的方法區一節會介紹。Java堆是垃圾收集器(GC)管理的主要區域。現在的收集器基本都采用分代收集算法:新生代和老年代。而對于不同的”代“采用的垃圾回收算法也不一樣。一般新生代使用復制算法;老年代使用標記整理算法。對于不同的”代“,一般使用不同的垃圾收集器,新生代垃圾收集器和老年代垃圾收集器配合工作。(關于垃圾收集算法、垃圾收集器以及堆中具體的分代等知識,我之后會專門寫幾篇博客來介紹。再次敬請期待?)
Java堆可以是物理上不連續的內存空間,只要邏輯上連續即可。Java堆可能拋出OutOfMemoryError異常。

方法區

方法區與Java堆一樣,是各個線程共享的內存區域。它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
所有的字節碼被加載之后,字節碼中的信息:類信息、類中的方法信息、常量信息、類中的靜態變量等都會存放在方法區。正如其名字一樣:方法區中存放的就是類和方法的所有信息。此外,如果一個類被加載了,就會在方法區生成一個代表該類的Class對象(唯一一種不在堆上生成的對象實例)該對象將作為程序訪問方法區中該類的信息的外部接口。有了該對象的存在,才有了反射的實現。
在Java7之前,HotSpot虛擬機中將GC分代收集擴展到了方法區,使用永久代來實現了方法區。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。但是在之后的HotSpot虛擬機實現中,逐漸開始將方法區從永久代移除。Java7中已經將運行時常量池從永久代移除,在Java 堆(Heap)中開辟了一塊區域存放運行時常量池。而在Java8中,已經徹底沒有了永久代,將方法區直接放在一個與堆不相連的本地內存區域,這個區域被叫做元空間。
關于元空間的更多信息,請參考:Java永久代去哪兒了

運行時常量池

運行時常量池是方法區的一部分,關于運行時常量池的介紹,請參考我的另一篇博文:String放入運行時常量池的時機與String.intern()方法解惑。我還是花了些時間在理解運行時常量池上的。

直接內存

JDK1.4中引用了NIO,并引用了Channel與Buffer,可以使用Native函數庫直接分配堆外內存,并通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內存的引用進行操作。
如上文介紹的:Java8以及之后的版本中方法區已經從原來的JVM運行時數據區中被開辟到了一個稱作元空間的直接內存區域。

參考:
《深入理解Java虛擬機-JVM高級特性與最佳實踐》第二版 周志明著

來自: http://blog.csdn.net/rainnnbow/article/details/50541079

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!