Java 虛擬機在執行 Java 程序的過程當中會把所管理的內存區域分紅以下區域:程序員
一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。緩存
每一個線程都有一個獨立的程序計數器,每一個計數器互不影響,獨立存儲,這類內存區域是線程私有內存。多線程
與程序計數器同樣,Java虛擬機棧(Java Vir-tual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。併發
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常;若是虛擬機棧能夠動態擴展(當前大部分的Java虛擬機均可動態擴展,只不過Java虛擬機規範中也容許固定長度的虛擬機棧),若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryError異常。佈局
虛擬機棧是爲 Java 方法服務的,本地方法棧是爲 native 方法服務的。優化
heap 是內存管理中最大的一個內存區域。線程
基本上全部對象實例都在堆中分配內存。3d
可是隨着JIT編譯器的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會致使一些微妙的變化發生,全部的對象都分配在堆上也漸漸變得不是那麼「絕對」了。指針
GC 主要回收的區域就在 heap 上。cdn
若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError異常。
方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
Runtime Constants Pool 是方法區的一部分。
Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。
直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域。可是這部份內存也被頻繁地使用,並且也可能致使OutOfMemoryError異常出現。
虛擬機遇到一個 new 指令,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已經被加載、解析過。沒有的話就會先加載、解析。
而後爲新生對象分配內存。
分配內存是隨機分配一款區域,因此 heap 中空閒內存和已用內存縱橫交錯。這種狀況虛擬機要維護一個空閒列表 (Free List),記錄哪些區域是能夠用的,分配時從表中查出一塊足夠大的區域給新生對象。
若是垃圾回收器帶壓縮功能,能夠把 heap 壓縮,全部用過的內存都放在一邊,空閒的內存放在另外一邊,中間放着一個指針做爲分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離,這種分配方式稱爲「指針碰撞」(Bump the Pointer)。
分配時多線程會有併發問題,避免併發:
分配完內存後,把內存空間初始化零值,保證了對象實例中的字段不賦值就有初始值。
而後對對象進行必要的設置,對象是哪一個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡。這些信息存在對象頭中。
執行 new 指令後會接着執行 init 方法,按程序員的意願進行初始化。對象才徹底建立完畢。
對象在內存中存儲的佈局分爲 3 塊:
Hotspot 的 Header 包括:
經過句柄訪問對象:
經過指針訪問對象:
使用句柄來訪問的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要修改。
使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷,因爲對象的訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是一項很是可觀的執行成本。
Hotspot 使用的是指針。