深入瞭解JVM&&閱讀筆記第二章

運行時數據區域

在這裏插入圖片描述

程序計數器

程序計數器是一個較小的空間,可看做是當前線程所執行的字節碼的行號指示器,是程序控制流的指示器,通過改變這個計數器的值來選取下一條需要執行的字節碼指令,從而實現分支、循環、跳轉、異常處理、線程恢復等工,因此線程都是獨立的,不同線程互不影響,獨立存儲。

程序計數器只記錄線程執行的JAVA方法,爲正在執行的虛擬機字節碼指令的地址

是唯一一個不會出現任何OutOfMemoryError的區域

JAVA虛擬機棧

Java虛擬機棧爲線程私有,生命週期同線程相同,描述的是Java方法執行的線程內存模型。
每一個方法被調用直到執行完畢的過程,實際上是一個棧幀在虛擬機棧從入棧到出棧的過程,當方法被執行時,Java虛擬機棧都會同步創建一個棧幀用來記錄存儲局部表量表、操作數棧、方法出口等信息。

棧幀的局部變量表存放了編譯器的各種Java虛擬機基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它並不等同於對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或者其他與此對象相關的位置returnAddress類型(指向了一條字節碼指令的地址)。

局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量空間是完全確定下來的,在方法運行期間不會改變局部變量表的大小。(這裏的大小指的是變量槽的數量,變量槽的具體大小實現由虛擬機自行決定)

這個區域會出現兩類異常:
· StackOverflowError:線程請求的棧深度大於虛擬機所能允許的深度
· OutOfMemoryError: 棧擴展時無法申請到足夠的內存(前提Java虛擬機棧容量可以動態擴展)

PS : HotSpot虛擬機的棧容量是不可以動態擴展的,以前的Classic虛擬機就可以,所以在HotSpot虛擬機上是不會由於虛擬機棧無法擴展而導致OutOfMemoryError異常——只要線程申請棧空間成功了就不會有OOM,但是如果申請時就失敗,仍然是會出現OOM異常的。

本地方法棧

本地方法棧於Java虛擬機棧作用類似,區別是虛擬機棧執行的Java方法,而本地方法棧執行的是Native方法

《Java虛擬機規範》對本地方法棧中方法使用的語言、使用方式與數據結構並沒有任何強制規定,因此具體的虛擬機可以根據需要自由實現它,甚至有的Java虛擬機(譬如Hot-Spot虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。與虛擬機棧一樣,本地方法棧也會在棧深度溢出或者棧擴展失敗時分別拋出StackOverflowError和OutOfMemoryError異常。

Java堆

Java堆(Java Heap)爲虛擬機管理的內存中最大的一塊,爲所有線程共享,作用是存放對象實例,“幾乎”所有的對象實例都在這裏分配內存。

Java堆是垃圾收集器管理的內存區域,即所謂的“GC堆”,現代垃圾收集器大部分是基於分代收集理論去設計的(像HotSpot,內部大部分垃圾收集器是基於分代思想),所以Java堆中經常會出現“新生代”“老年代”“永久代”“Eden空間”“From Survivor空間”“To Survivor空間”等名詞,但這只是一種理念,並非每個Java虛擬機堆內存都會劃分代

從分配內存的角度看,所有線程共享的Java堆中可以劃分出多個線程私有的分配緩衝區,提升對象分配時的效率。不過無論從什麼角度劃分,無論是哪個區域,都不會改變Java堆中存儲內容的共性,存儲的都只能是對象的實例,將Java堆細分的目的只是爲了更好地回收內存,或者更快地分配內存。

Java堆從物理角度上可以處於不連續的內存空間,但是在邏輯上它是連續的,但對於大對象(如數組對象),多數虛擬機出於簡單、效率,會要求連續的空間。

Java堆既可以被實現成固定大小的,也可以是可擴展的,不過當前主流的Java虛擬機都是按照可擴展來實現的(通過參數-Xmx和-Xms設定)。如果在Java堆中沒有內存完成實例分配,並且堆也無法再擴展時,Java虛擬機將會拋出OutOfMemoryError異常。

方法區

方法區是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等數據。《Java虛擬機規範》中把方法區描述爲堆的一個邏輯部分,但是它卻有一個別名叫作“非堆”(Non-Heap),目的是與Java堆區分開來。

方法區在以前經常被稱爲“永生代”,實際並不等價。這個說法主要是當時的HotSpot虛擬機,其把收集器的分代設計擴展至方法區,或者說使用永久代來實現方法區而已,使得HotSpot的垃圾收集器能夠像管理Java堆一樣管理這部分內存。但是其他虛擬機不存在永久代的概念的。原則上如何實現方法區屬於虛擬機實現細節,不受《Java虛擬機規範》管束,並不要求統一。

但是,用永久代來實現方法區,會存在更容易遇到內存泄漏的問題(永久代有-XX:MaxPermSize的上限,即使不設置也有默認大小,而J9和JRockit只要沒有觸碰到進程可用內存的上限,例如32位系統中的4GB限制,就不會出問題)。到了JDK 7的HotSpot,已經把原本放在永久代的字符串常量池、靜態變量等移出,而到了JDK 8,終於完全廢棄了永久代的概念,改用與JRockit、J9一樣在本地內存中實現的元空間(Meta-space)來代替,把JDK 7中永久代還剩餘的內容(主要是類型信息)全部移到元空間中。

垃圾收集行爲在方法區較少出現,但並非數據進入了方法區就如永久代的名字一樣“永久”存在了。如果方法區無法滿足新的內存分配需求時,將拋出OutOfMemoryError異常。

運行時常量池

方法區的一部分。Class文件中除了類的版本、字段、方法、接口等描述信息外,還有常量池,用於存放編譯期生成的各種字面量與符號引用,在類加載後存放到方法區的運行時常量池中

運行時常量池具備動態性,常量並不一定是編譯期間才放入常量池,運行期間也可以將新的常量放入池(String類的intern()方法)。

運行時常量池是方法區的一部分,受到方法區內存的限制,當常量池無法再申請到內存時會拋出OutOfMemoryError異常。

直接內存

直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是《Java虛擬機規範》中定義的內存區域。但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現。

在JDK 1.4中新加入了NIO(New Input/Output)類,一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,通過Native函數庫直接分配堆外內存,然後通過一個在Java堆裏面的DirectByteBuffer對象作爲這塊內存的引用進行操作,避免了在Java堆和Native堆中來回複製數據。

PS:Netty正是基於NIO,封裝了NIO的繁雜操作

顯然,本機直接內存的分配不會受到Java堆大小的限制,但是,既然是內存,則肯定還是會受到本機總內存(包括物理內存、SWAP分區或者分頁文件)大小以及處理器尋址空間的限制,一般服務器管理員配置虛擬機參數時,會根據實際內存去設置-Xmx等參數信息,但經常忽略掉直接內存,使得各個內存區域總和大於物理內存限制(包括物理的和操作系統級的限制),從而導致動態擴展時出現OutOfMemoryError異常。

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