【轉】JVM 運行時的內存分配

  首先我們必須要知道的是 Java 是跨平臺的。而它之所以跨平臺就是因爲 JVM 不是跨平臺的。JVM 建立了 Java 程序和操作系統之間的橋樑,JVM 是用 C 語言編寫,而 C 語言不具備跨平臺的特性。所以對於 Windows 平臺,Java 有基於 Windows 平臺的 JVM;對於 Linux 平臺,Java 也有基於 Linux 平臺的 JVM等等。不同的操作系統有不同的 JVM,所以我們編寫的 Java 代碼能在各個平臺上運行,是因爲有各個平臺的 JVM。

  而 Java 的內存分配也是在 JVM 中進行的。JVM 是 Java 內存分配的原理和前提。

Java 程序爲了提高程序的效率,對數據進行了不同空間的分配,具體劃分爲如下 5 個內存空間。

            

 

1、程序計數器(Program Counter Register)

  它是一塊較小的內存空間,它的作用可以看做是當先線程所執行的字節碼的信號指示器。每一條JVM線程都有自己的PC寄存器,各條線程之間互不影響,獨立存儲,這類內存區域被稱爲“線程私有”內存在任意時刻,一條JVM線程只會執行一個方法的代碼。該方法稱爲該線程的當前方法(Current Method);如果該方法是java方法,那PC寄存器保存JVM正在執行的字節碼指令的地址;如果該方法是native,那PC寄存器的值是undefined。此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。

 

2、Java虛擬機棧(Java Virtual Machine Stack)

  與PC寄存器一樣,Java虛擬機棧也是線程私有的。每一個JVM線程都有自己的java虛擬機棧,這個棧與線程同時創建,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。JVM stack 可以被實現成固定大小,也可以根據計算動態擴展。如果採用固定大小的JVM stack設計,那麼每一條線程的JVM Stack容量應該在線程創建時獨立地選定。JVM實現應該提供調節JVM Stack初始容量的手段;如果採用動態擴展和收縮的JVM Stack方式,應該提供調節最大、最小容量的手段。如果線程請求的棧深度大於虛擬機所允許的深度將拋出StackOverflowError;如果JVM Stack可以動態擴展,但是在嘗試擴展時無法申請到足夠的內存時拋出OutOfMemoryError。

通常來說棧存放的是局部變量,包括:

  ①、保存基本數據類型的值

  ②、保存對象的引用

 

3、本地方法棧(Native Method Stack)

  本地方法棧與虛擬機棧作用相似,後者爲虛擬機執行Java方法服務,而前者爲虛擬機用到的Native方法服務。虛擬機規範對於本地方法棧中方法使用的語言,使用方式和數據結構沒有強制規定,甚至有的虛擬機(比如HotSpot)直接把二者合二爲一。本地方法棧拋出的異常跟上面的虛擬機棧一樣。

 

4、Java堆(Java Heap)

  虛擬機管理的內存中最大的一塊,同時也是被所有線程所共享的,它在虛擬機啓動時創建,這貨存在的意義就是存放對象實例,幾乎所有的對象實例以及數組都要在這裏分配內存。這裏面的對象被自動管理,也就是俗稱的GC(Garbage Collector)所管理。用就是了,有GC扛着呢,不用操心銷燬回收的事兒。Java堆的容量可以是固定大小,也可以隨着需求動態擴展(-Xms和-Xmx),並在不需要過多空間時自動收縮。Java堆所使用的內存不需要保證是物理連續的,只要邏輯上是連續的即可。JVM實現應當提供給程序員調節Java 堆初始容量的手段,對於可動態擴展和收縮的堆來說,則應當提供調節其最大和最小容量的手段。如果堆中沒有內存完成實例分配並且堆也無法擴展,就會拋OutOfMemoryError。

  堆存放的是所有 new 出來的東西,注意:這裏創建出來的對象只包括屬於各自的 成員變量,不包括成員方法。因爲同一個類的對象擁有各自的成員變量,存儲在各自的堆中,但是他們共享該類的方法,並不是每創建一個對象就把成員變量複製一遍。

 

5、方法區(Method Area

  跟堆一樣是被各個線程共享的內存區域,用於存儲以被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然這個區域被虛擬機規範把方法區描述爲堆的一個邏輯部分,但是它的別名叫非堆,用來與堆做一下區別。方法區在虛擬機啓動的時候創建。方法區的容量可以是固定大小的,也可以隨着程序執行的需求動態擴展,並在不需要過多空間時自動收縮。方法區在實際內存空間中可以是不連續的。Java虛擬機實現應當提供給程序員或者最終用戶調節方法區初始容量的手段,對於可以動態擴展和收縮方法區來說,則應當提供調節其最大、最小容量的手段。當方法區無法滿足內存分配需求時就會拋OutOfMemoryError。

  5.1 運行時常量池(Runtime Constant Pool)

  它是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後存放到方法區的運行時常量池中。Java虛擬機對Class文件的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個字節用於存儲哪種數據都必須符合規範上的要求,這樣纔會被虛擬機認可、裝載和執行。但對於運行時常量池,Java虛擬機規範沒有做任何細節的要求,不同的提供商實現的虛擬機可以按照自己的需要來實現這個內存區域。不過,一般來說,除了保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。運行時常量池相對於Class文件常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。既然運行時常量池是方法區的一部分,自然會受到方法區內存的限制,當常量池無法再申請到內存時會拋出OutOfMemoryError異常。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章