《深刻理解java虛擬機》學習筆記系列——java內存區域劃分

Java 運行時數據區域的學習,是學習 jvm 以及 GC 機制的基礎,也是深刻理解 java 對象建立及運行過程的前提。
廢話很少說,直接進入正題:java

一張圖總結

圖片描述

詳細介紹

程序計數器

概念

程序計數器是一個比較小的內存區域,用於指示當前線程所執行的字節碼執行到了第幾行,能夠理解爲是當前線程的行號指示器。字節碼解釋器在工做時,會經過改變這個計數器的值來取下一條語句指令。算法

做用

因爲 Java 虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(嚴謹點,多核處理器時指其中一個內核),只會執行一條線程的指令。segmentfault

所以,爲了實現線程切換後回覆到正確的執行位置,各個線程私有的程序計數器時必不可少的。數組

注: 若是程序執行的是一個Java方法,則計數器記錄的是正在執行的虛擬機字節碼指令地址;若是正在執行的是一個本地(native,由C語言編寫完成)方法,則計數器的值爲Undefined多線程

另: 因爲程序計數器只是記錄當前指令地址,因此不存在內存溢出的狀況,所以,程序計數器也是全部JVM內存區域中惟一一個沒有定義OutOfMemoryError的區域。jvm

Java 虛擬機棧

概念

一個線程的每一個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操做站、動態連接、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。性能

做用

局部變量表中存儲着方法的相關局部變量,包括各類基本數據類型,對象的引用,返回地址等。在局部變量表中,只有long和double類型會佔用2個局部變量空間(Slot,對於32位機器,一個Slot就是32個bit),其它都是1個Slot。學習

須要注意的是,局部變量表是在編譯時就已經肯定好的,方法運行所須要分配的空間在棧幀中是徹底肯定的,在方法的生命週期內都不會改變優化

注: 每一個線程對應着一個虛擬機棧,所以虛擬機棧也是線程私有的。編碼

另: 虛擬機棧中定義了兩種異常,若是線程調用的棧深度大於虛擬機容許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數Java虛擬機都容許動態擴展虛擬機棧的大小(有少部分是固定長度的),因此線程能夠一直申請棧,直到內存不足,此時,會拋出OutOfMemoryError(內存溢出)。

本地方法棧

本地方法棧在做用,運行機制,異常類型等方面都與虛擬機棧相同,惟一的區別是:虛擬機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在不少虛擬機中(如Sun的JDK默認的HotSpot虛擬機),會將本地方法棧與虛擬機棧放在一塊兒使用。

本地方法棧也是線程私有的。

Java 堆

概念

JVM用來存儲對象實例以及數組值的區域,能夠認爲Java中全部經過new建立的對象的內存都在此分配,Heap中的對象的內存須要等待GC進行回收(不過現代技術裏,也不是這麼絕對的,也有棧上直接分配的)。

補充介紹

(1)堆是JVM中全部線程共享的,所以在其上進行對象內存的分配均須要進行加鎖,這也致使了new對象的開銷是比較大的

(2)Sun Hotspot JVM爲了提高對象內存分配的效率,對於所建立的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的狀況計算而得,在TLAB上分配對象時不須要加鎖,所以JVM在給線程的對象分配內存時會盡可能的在TLAB上分配,在這種狀況下JVM中分配對象內存的性能和C基本是同樣高效的,但若是對象過大的話則仍然是直接使用堆空間分配

(3)TLAB僅做用於新生代的Eden Space,所以在編寫Java程序時,一般多個小的對象比大的對象分配起來更加高效。

關於堆區的內容還有不少,在後續的垃圾回收算法中還有更多的介紹。

方法區

概念

方法區是各個線程共享的區域,用於存儲已經被虛擬機加載的類信息(即加載類時須要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態變量、編譯器即時編譯的代碼等。

補充介紹

方法區在物理上也不須要是連續的,能夠選擇固定大小或可擴展大小,而且方法區比堆還多了一個限制:能夠選擇是否執行垃圾收集。通常的,方法區上執行的垃圾收集是不多的,這也是方法區被稱爲永久代的緣由之一(HotSpot),但這也不表明着在方法區上徹底沒有垃圾收集,其上的垃圾收集主要是針對常量池的內存回收和對已加載類的卸載。

在方法區上定義了OutOfMemoryError:PermGen space異常,在內存不足時拋出。

運行時常量池

概念

方法區的一部分,用於存儲編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個變量、接口的位置,直接引用就是根據符號引用翻譯出來的地址,將在類連接階段完成翻譯);運行時常量池除了存儲編譯期常量外,也能夠存儲在運行時間產生的常量(好比String類的intern()方法,做用是String維護了一個常量池,若是調用的字符「abc」已經在常量池中,則返回池中的字符串地址,不然,新建一個常量加入池中,並返回地址)

總結

本系列文章將從4個方面介紹Java GC機制,1,內存是如何分配的;2,如何保證內存不被錯誤回收(即:哪些內存須要回收);3,在什麼狀況下執行GC以及執行GC的方式;4,如何監控和優化GC機制。

瞭解 Java 內存區域劃分,是學習 GC 概念的前提。解鈴還需繫鈴人,相信完整學習完了底層的垃圾回收機制,讀者也將對 java 世界中對象建立與運行的原理有個清晰的認識吧:-)

參考文檔

聯繫做者

zhihu.com
segmentfault.com
oschina.net

相關文章
相關標籤/搜索