重識JVM(一):運行時數據區域

記得上一次看jvm相關的知識還是在大四的時候,兩年過去了,記憶已經逐漸模糊。現在來重識一下jvm,希望溫故而知新,一些重要的知識點我會在博客上把學習的知識記錄下來。

想要了解jvm,就一定需要了解java虛擬機是如何使用內存的,要不在出現內存泄漏和內存溢出等問題時,我們是沒法準確地排查出錯誤的,下面就讓我一起來重新認識一下java虛擬機在執行java程序的過程中的運行時數據區。

參照上圖,我們來進一步瞭解一下運行時數據區中各個區域的作用和詳細情況。

一.程序計數器

1.從最簡單的程序計數器說起,程序計數器就是當前線程執行字節碼的行號指示器,字節碼解釋器工作時就是通過改變這個計數器的值來選去下一跳需要執行的字節碼指令, 分支, 循環, 跳轉, 異常處理,線程恢復等基礎功能都需要依賴這個計數器來完成。

2.每條線程都需要有一個獨立的程序計數器, 各條線程之間的計數器互不影響, 獨立存儲, 我們稱這類內存區域爲"線程私有內存"。

3.如果線程正在執行的是一個Java方法, 這個計數器記錄的是正在執行的虛擬機字節碼指令的地址; 如果正在執行的是Native方法, 這個計數器值則爲空(Undefined)。此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。

二.java虛擬機棧

1.與程序計數器一樣, Java虛擬機棧也是線程私有的, 它的生命週期與線程相同

2.每個方法被執行的時候都會同時創建一個棧幀用於存儲 局部變量表, 操作棧, 動態鏈接, 方法出口等信息.。每一個方法被調用直至執行完成的過程, 就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。

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

4.如果線程請求的棧深度大於虛擬機所允許的深度, 將拋出StackOverflowError異常; 如果虛擬機棧可以動態擴展(當前大部分Java虛擬機都可動態拓展,只不過Java虛擬機規範中也允許固定長度的虛擬機棧),當拓展時無法申請到足夠的內存時會拋出OutOfMemoryError異常。

三.本地方法棧

本地方法棧和虛擬機棧發揮的作用十分相似。同樣是線程私有,它們之間的區別不過是虛擬機棧爲Java 方法服務,而本地方法棧爲虛擬機使用到的Native 方法服務。與虛擬機棧一樣,本地方法棧也會拋出StackOverflowError 異常和OutOfMemoryError 異常。

四.堆

1.Java堆是被所有線程共享的一塊內存區域, 在虛擬機啓動時創建。 此內存區域的唯一目的就是存放對象實例, 幾乎所有的對象實例都在這裏分配內存。 這一點在Java虛擬機規範中描述的是: 所有的對象實例以及數組都要在堆上分配, 但是隨着 JIT編譯器的發展與逃逸分析技術(通過分析若一個對象沒有逃逸出一個方法,那麼該對象在棧上分配空間,該對象隨着棧的銷燬而銷燬)的逐漸成熟, 棧上分配, 標量替換優化技術(將部分字段使用標量存儲)將會導致一些微妙的變化發生, 所有的對象都分配在堆上也逐漸變得不是那麼"絕對"了

2.Java 堆是垃圾收集器管理的主要區域, 因此很多時候也被稱做"GC堆"(Garbage Collected Heap), 如果從內存回收的角度看, 由於現在收集器基本都是採用的分代收集算法, 所以Java堆中還可以細分爲: 新生代和老年代; 新生代中有Eden空間, From Survivor空間, To Survivor空間。堆空間內存分配(默認情況下)老年代 : 三分之二的堆空間,新生代代 : 三分之一的堆空間,eden區: 8/10 的新生代空間,From Survivor : 1/10 的新生代空間,To Survivor: 1/10 的新生代空間。

3.根據Java虛擬機規範的規定, Java堆上可以處於物理上不連續的內存空間中, 只要邏輯上是連續的即可(當空間中不連續的空間越來越多,可能會發生明明剩餘空間大於需要申請的空間卻申請失敗), 就像我們的磁盤空間一樣。 在實現時, 既可以實現成固定大小的, 也可以是可拓展的, 不過當前主流的虛擬機都是按照可拓展來實現的( 可設置-Xms 初始化堆, -Xmx 最大堆空間), 如果在堆中沒有內存完成實例分配, 並且堆也無法在拓展時, 將會拋出OutOfMemoryError異常。

五.方法區

1.方法區(Method Area) 與Java堆一樣, 是各個線程共享的內存區域, 它用於存儲已被虛擬機加載的類信息, 常量, 靜態變量, 即時編譯器編譯後的代碼等數據。 雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分, 但是它卻有一個別名叫做Non-Heap非堆, 目的應該是與Java Heap 區分開來。

2.在JDK1.7以前HotSpot虛擬機使用永久代來實現方法區。

3.在JDK1.7中 存儲在永久代的部分數據就已經轉移到Java Heap或者Native memory。譬如符號引用(Symbols)轉移到了native memory,原本存放在永久代的字符常量池移出。但永久代仍存在於JDK 1.7中,並沒有完全移除。

4.JDK1.8中進行了較大改動,在Java8 中,永久代被刪除,方法區的HotSpot 的實現爲Metaspace 元數據區,不放在虛
擬機中而放在本地內存中,存儲類的元信息;而將類的靜態變量(放在Class 對象中)和運行時常量池放在中。

六.運行時常量池

Class文件中除了有類的版本, 字段,方法, 接口等描述信息外, 還有一項信息是常量池(Constant Pool Table), 用於存放編譯期生成的各種字面量和符號引用, 這部分內容將在類加載後存放到方法區的運行時常量池中

七.直接內存

1.在JDK1.4 中新加入的NIO類,引入了一種基於通道(Channel)和緩衝區(Buffer)的I/O 形式,他可以使用Native 函數直接分配堆外內存,然後通過一個存儲在Java 堆中的DirectByteBuffer 對象作爲這塊內存的引用進行操作。這樣能在一些場所顯著提高性能,因爲避免了在Java 堆和Native 堆中來回複製數據。

2.直接內存(Direct Memory) 並不是虛擬機運行時數據區的一部分, 也不是Java虛擬機規範中定義的內存區域, 但是這部分內存也被頻繁地使用, 而且也可能導致OutOfMemoryError異常出現。 顯然, 本機直接內存的分配不會受到Java堆大小的限制, 但是, 既然是內存, 則肯定還是會受到本機總內存的大小及處理器尋址空間的限制。 服務器管理員配置虛擬機參數時, 一般會根據實際內存-Xmx等參數信息, 但經常會忽略到直接內存, 使得各個內存區域的總和大於物理內存限制(包括物理上的和操作系統級的限制), 從而導致動態擴展時出現OutOfMemoryError異常。

參考資料:《深入裂解java虛擬機》以及jdk1.8相關信息。

 

 

 

 

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