Java虛擬機所管理的記憶體包括一下幾個運行時資料區域:

其中,方法區、堆是執行緒共享的,虛擬機堆疊、本地方法堆疊、程式計數器 是執行緒私有的,
1. 程式計數器
程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成,
由于Java虛擬機的多執行緒是通過執行緒輪流切換并分配處理器執行時間的方式來實作的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)都只會執行一條執行緒中的指令,因此,為了執行緒切換后能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間計數器互不影響,獨立存盤,我們稱這類記憶體區域為“執行緒私有”的記憶體,
此記憶體區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域,
2.Java虛擬機堆疊
與程式計數器一 樣,Java虛擬機堆疊也是執行緒私有的, 它的生命周期與執行緒相同,虛擬機堆疊描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會創建一個堆疊幀用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,每一個方法從呼叫直至執行完成的程序,就對應著一個堆疊幀在虛擬機堆疊中入堆疊到出堆疊的程序,
在編譯程式代碼的時候,堆疊幀中需要多大的區域變數表,多深的運算元堆疊都已經完全確定了并且寫入到方法表的Code屬性之中,因此一個堆疊幀需要分配多少記憶體,不會受到程式運行期變數資料的影響,而僅僅取決于具體的虛擬機實作,
區域變數表存放了編譯期可知的各種基本資料型別(boolean、byte、 char、 short、 int、float、long、 double)、 物件參考(reference 型別,它不等同于物件本身,可能是-一個指向物件起始地址的參考指標,也可能是指向一個代表物件的句柄或其他與此物件相關的位置)和returmAddress型別(指向了一條位元組碼指令的地址),
其中64位長度的long和double型別的資料會占用2個區域變數空間(Slot), 其余的資料型別只占用1個,區域變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域變數空間是完全確定的,在方法運行期間不會改變區域變數表的大小,
在Java虛擬機規范中,對這個區域規定了兩種例外狀況:如果執行緒請求的堆疊深度大于虛擬機所允許的深度,將拋出StackOverflowError例外;如果虛擬機堆疊可以動態擴展(當前大部分的Java虛擬機都可動態擴展,只不過Java虛擬機規范中也允許固定長度的虛擬機堆疊),如果擴展時無法申請到足夠的記憶體,就會拋出OutOfMemoryError例外,
3.本地方法堆疊
本地方法堆疊與虛擬機堆疊所發揮的作用是非常相似的,它們之間的區別不過是虛擬機堆疊為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法堆疊則為虛擬機使用到的Native方法服務,
在虛擬機規范中對本地方法堆疊中方法使用的語言、使用方式與資料結構并沒有強制規定,因此具體的虛擬機可以自由實作它,甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法堆疊和虛擬機堆疊合二為一,與虛擬機堆疊-樣,本地方法堆疊區域也會拋出StackOverflowEror和OutOfMeroryError例外,
4.堆
對于大多數應用來說,Java堆是Java虛擬機所管理的記憶體中最大的一塊,Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機啟動時創建,此記憶體區域的唯一目的就是存放物件實體,幾乎所有的物件實體以及陣列都要在堆上分配,
Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”,從記憶體回收的角度來看,由于現在收集器基本都采用分代收集演算法,所以Java堆中還可以細分為:新生代和老年代;再細致一點的有Eden空間、From Survivor空間、To Survivor空間等,從記憶體分配的角度來看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩沖區,不過無論如何劃分,都與存放內容無關,無論哪個區域,存盤的都仍然是物件實體,進一步劃分的目的是為了更好地回收記憶體,或者更快地分配記憶體,
根據Java虛擬機規范的規定,Java 堆可以處于物理上不連續的記憶體空間中,只要邏輯上是連續的即可,就像我們的磁盤空間一樣,在實作時,既可以實作成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實作的(通過-Xmx和Xms控制),如果在堆中沒有記憶體完成實體分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError例外,
5.方法區
方法區與Java堆一樣, 是各個執行緒共享的記憶體區域,它用于存盤已被虛擬機加載的類資訊、常量、靜態變數、即時編譯器編譯后的代碼等資料,
相對而言,垃圾收集行為在這個區域是比較少出現的,但并非資料進入了方法區就如永久代的名字一樣“永久”存在了,這區域的記憶體回收目標主要是針對常量池的回收和對型別的卸載,一般來說,這個區域的回收“成績”比較難以令人滿意,尤其是型別的卸載,條件相當苛刻,但是這部磁區域的回收確實是必要的,在Sun公司的BUG串列中,曾出現過的若干個嚴重的BUG就是由于低版本的HotSpot虛擬機對此區域未完全回收而導致記憶體泄漏,根據Java虛擬機規范的規定,當方法區無法滿足記憶體分配需求時,將拋出OutOfMemoryError例外,
(1) 運行時常量池
運行時常量池是方法區的一部分,Class 檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用于存放編譯期生成的各種字面量和符號參考,這部分內容將在類加載后進人方法區的運行時常量池中存放,一般來說,除了保存Class檔案中描述的符號參考外,還會把翻譯出來的直接參考也存盤在運行時常量池中,
6.直接記憶體
直接記憶體并不是虛擬機運行時資料區的一部分,也不是Java虛擬機規范中定義的記憶體區域,但是這部分記憶體也被頻繁地使用,而且也可能導致OutOfMemoryError例外出現,所以我們放到這里一起講解,在JDK 1.4中新加入了NIO (New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(Buffer) 的I/O方式,它可以使用Native函式庫直接分配堆外記憶體,然后通過一個存盤在Java堆中的DirectByteBuffer物件作為這塊記憶體的參考進行操作,這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制資料,顯然,本機直接記憶體的分配不會受到Java堆大小的限制,但是,既然是記憶體,肯定還是會受到本機總記憶體(包括RAM以及SWAP區或者分頁檔案)大小以及處理器尋址空間的限制,服務器管理員在配置虛擬機引數時,會根據實際記憶體設定-Xmx等引數資訊,但經常忽略直接記憶體,使得各個記憶體區域總和大于物理記憶體限制(包括物理的和作業系統級的限制),從而導致動態擴展時出現OutOfMemoryError例外,
-- 參考資料:
《深入理解Java虛擬機--JVM高級特性與最佳實踐 第2版》 --周志明 著
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/125380.html
標籤:Java
上一篇:OOM記憶體溢位場景及分析
下一篇:JVM類加載機制
