一點一滴探究 JVM 以內存結構

前言

我一直嘗試着用不同的文字來寫博客!緣由很簡單,你講的知識書上都有,那麼每一個人爲何不選擇看書而選擇看你的博文來學習呢?由於書上的內容都是大片大片描述性的文字,對於jvm這塊的知識,又是異常枯燥,但又不能不學習的硬骨頭!這剛好也就能說明Head First系列的書籍爲何比較火的緣由,每一個人接收圖形知識的速度每每比文字性的東西要快不少。從此我也會嘗試用本身的特點來寫博客,儘可能能引發讀者的興趣,能從中學到東西,我就滿足了!java

今天的一點一滴探究JVM系列,打算複習一下jvm內存結構!至於學習這塊知識的好處?一,從面試的角度來看,你瞭解jvm,而且java基礎紮實,你才更有競爭力(由於我本人本科還沒畢業,因此考慮問題常常從面試者的角度來考慮)。其二,提升你對java的理解,知道你建立的每個對象,每個變量,都在什麼地方,若是不知道這些稀裏糊塗得寫代碼,總會有一天會」翻車」的!好了,廢話很少說了,咱們開始正題吧!c++

開始以前

Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的」牆」, 牆外的人想進去,牆內的人想出來。 或許你常常看到StackOverFlowError, OutOfMemoryError無從下手,由於你壓根不知道,到底是什麼東西形成內存爆了,固然,你也沒法解決!面試

舉個簡單的例子bash

public class test {
    private int f() {
        f();
    }
    public static void main(String[] args) {
        f();
    }
}
複製代碼

這個簡單的遞歸,不對,它不算是遞歸,由於沒有終止條件,可是你知道它最終會報什麼錯誤,知道爲何會報這個錯誤嗎?到底是那塊內存發生了錯誤?多線程

這個問題,咱們留在後面回答,是留在後面你本身解答,看完這篇博文,不用我說,這些問題你都會很清楚!相信我!架構

目標

你可能會好奇,你看完這篇文章你能學到什麼?jvm

清楚你的對象會被分配在哪裏(不絕對) 理解哪些區域對線程來講是私有區,哪些區域是線程共享區域 知道方法調用發生了什麼? … 等等等,你可能還會解釋你之前遇到一些匪夷所思的問題!總之,你若是以前沒了解過這些知識,那麼這些東西對你來講,就是成長!學習

牆內的世界

你可能很好奇,牆內到底是什麼樣?接下來跟着我一探究竟ui

上圖就是jvm比較詳細的內存劃分,下面咱們來按線程私有共享來劃分jvm內存區spa

下面咱們來着重介紹一下這幾塊內存區域

程序計數器(Program Counter Register)

什麼是程序計數器呢,學過彙編的都知道,cs:ip組成的物理地址是下一條要執行的指令的地址,來吧!看圖

咱們能夠很清楚的看到,當前cs:ip指向的內存地址剛好就是咱們要執行的下一條指令的位置,前面咱們圖中(按線程私有共享劃分jvm內存的圖)又說了,程序計數器是線程私有的,再聯想一下我舉cs:ip的例子,咱們能夠很天然的想到,程序計數器其實就是記錄線程當前執行到了哪一條指令,由於什麼要記錄這個值呢?由於,若是咱們有不少個線程,線程執行順序又是不可預料的,假如某一時刻咱們在執行線程A裏面的指令,而後線程B又得到了cpu的資源,去執行去線程B的指令,假如再過了一段時間以後,A又得到了cpu的資源,想吃回頭草,此時回到線程A執行,它不知道要執行線程A的哪條指令!這是沒有程序計數器所造成的尷尬局面,可是有了線程私有的程序計數器,這個問題就不存在了,這就是程序計數器出現的緣由,以及它的用處,我想你看完這段文字,應該已經對程序計數器這個概念徹底理解了!

另外,我須要說明的一點是,程序計數器是Java虛擬機規範中惟一一個沒有規定任何內存錯誤的區域!

虛擬機棧(Vm Stack)

這塊區域是幹啥的?爲啥也是線程私有的?

虛擬機棧描述的是Java方法執行的內存模型 咱們來解讀這句話,爲何說Vm Stack是描述Java方法執行的內存模型呢?其實:

每一個方法執行的時候都會建立一個棧幀(Stack Frame)的東西,學過c/c++的應該都對這個概念熟悉。棧幀用於存儲局部變量表、操做數棧、動態連接、方法出口信息等。每一個方法從調用開始到結束的過程,都對應這Vm Stack中的入棧出棧的過程!這也就能回答開頭咱們看到的那個問題了,很簡單錯誤在單線程狀況下確定是StackOverFlowError,多線程下OutOfMemoryError(上圖已經寫得很清楚了)

好比

public void test() {
    String name = "stormma";
    int age = 21;
}
JAVA架構羣:678779467
複製代碼

上面的例子的age變量和name引用都是存儲在虛擬機棧的棧幀裏面的(由於咱們前面說過了,一個方法從開始調用到結束調用的過程都對應着一個Vm Stack出棧入棧的過程)。

咱們前面說了,這塊區域存儲了局部變量表,操做數棧,動態連接,還有方法出口信息等,我想你應該比較好奇這幾個概念。

局部變量表: 局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量,其中存放的數據的類型是編譯期可知的各類基本數據類型、對象引用(reference)和(returnAddress)類型(它指向了一條字節碼指令的地址)。局部變量表所需的內存空間在編譯期間完成計算的,即在Java程序被編譯成Class文件時,就肯定了所需分配的最大局部變量表的容量。當進入一個方法時,這個方法須要在棧中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。

操做數棧: 操做數棧又常被稱爲操做棧,操做數棧的最大深度也是在編譯的時候就肯定了。32位數據類型所佔的棧容量爲1, 64位數據類型所佔的棧容量爲2。當一個方法開始執行時,它的操做棧是空的,在方法的執行過程當中,會有各類字節碼指令(好比:加操做、賦值元算等)向操做棧中寫入和提取內容,也就是入棧和出棧操做。Java虛擬機的解釋執行引擎稱爲「基於棧的執行引擎」,其中所指的「棧」就是操做數棧。所以咱們也稱Java虛擬機是基於棧的,這點不一樣於Android虛擬機,Android虛擬機是基於寄存器的。基於棧的指令集最主要的優勢是可移植性強,主要的缺點是執行速度相對會慢些;而因爲寄存器由硬件直接提供,因此基於寄存器指令集最主要的優勢是執行速度快,主要的缺點是可移植性差

動態連接: 每一個棧幀都包含一個指向運行時常量池(在方法區中,後面介紹)中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。Class文件的常量池中存在有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用,一部分會在類加載階段或第一次使用的時候轉化爲直接引用(如 final、static 域等),稱爲靜態解析,另外一部分將在每一次的運行期間轉化爲直接引用,這部分稱爲動態鏈接。

方法返回地址: 當一個方法被執行後,有兩種方式退出該方法:執行引擎遇到了任意一個方法返回的字節碼指令或遇到了異常,而且該異常沒有在方法體內獲得處理。不管採用何種退出方式,在方法退出以後,都須要返回到方法被調用的位置,程序才能繼續執行。方法返回時可能須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。通常來講,方法正常退出時,調用者的PC計數器的值就能夠做爲返回地址,棧幀中極可能保存了這個計數器值,而方法異常退出時,返回地址是要經過異常處理器來肯定的,棧幀中通常不會保存這部分信息。方法退出的過程實際上等同於把當前棧幀出站,所以退出時可能執行的操做有:恢復上層方法的局部變量表和操做數棧,若是有返回值,則把它壓入調用者棧幀的操做數棧中,調整PC計數器的值以指向方法調用指令後面的一條指令。 我想關於這個區域的東西我已經介紹完了,我想你也應該懂了。

下面咱們來下一個區域: 堆(heap)

堆(Heap)

堆區,是一塊頗有意思的區域,爲啥有意思,由於這塊區域是全部線程共享的,也是咱們大部分的對象的聚居地(爲啥說是大部分呢?這個概念咱們以後的文章會進行詳細的講解,若是你特別好奇,能夠看一下我以前的文章, Java逃逸分析)!也是jvm管理的最大一塊內存(對了,上面的圖的大小不表明內存佔比,只是爲了看着舒服而已)!也是gc開展工做的主要區域。

堆內存中分爲一塊區域,用於存儲類信息,靜態變量等等數據,這一塊區域以前叫作方法區後面又叫永久帶,以後更名叫作Meta-Area/Meta Space Area,元數據空間,名字不重要,咱們要清楚這塊區域是什麼做用就好了!

Meta-Area

這塊區域也是線程共享的區域,它主要存儲jvm加載類的類信息,類變量,常量(這個在meta-area的常量區),即時編譯器編譯後的代碼等數據。

運行時常量區

這個區域是Meta-Area的一部分,用於存放編譯器生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。這在咱們的上一篇博客有所涉及。

枯燥概念性的東西看完以後,咱們來看一個例子,來加深一下這塊的印象:

public void test() {
    Object obj = new Object();
}
JAVA架構羣:678779467
複製代碼

對於這段代碼會涉及Vm Stack、Java Heap、Meta-Area三個最重要的內存區域。

結合咱們前面的例子,由於test()方法涉及到Vm Stack區,我想你應該明白,obj會存放在局部變量表中,new Object(),咱們前面說過咱們大部分的對象都會存儲在Java Heap這個區域,因此,Java Heap存儲了這個實例對象!那麼你會很好奇,Meta-Area爲啥會涉及到呢?

咱們知道Meta-Area存儲了類的信息,類變量常量等等東西!由於咱們實例化Object對應的時候,要用到Object這個類的信息,因此它會訪問Meta-Area的Object.class這個Class對象來得到一些實例化對象須要的東西。

對了,做爲補充,我想你還須要知道, obj引用怎麼你能訪問到Java Heap區的那個實例化對象

有兩種方式,一種使用過句柄指針(學過c/c++對這些概念應該會很熟悉)

還有一種就是經過指針直接訪問

上圖來自深刻理解JVM一書

本地方法棧(Native Method Stack)

這塊區域相對來講,沒有前面幾個概念重要。

該區域與虛擬機棧所發揮的做用很是類似,只是虛擬機棧爲虛擬機執行Java方法服務,而本地方法棧則爲使用到的本地操做系統(Native)方法服務。

好比Java調用c/c++/彙編就用到這塊區域

結尾

我想你看完這篇博文,應該達到了咱們文章開始以前的目標!這篇文章介紹的比較淺顯,本着用例子來解釋說明內存區域的做用,這樣我想你會更容易接收,總比大片的文字描述讓你更有興趣!若是你有什麼建議或者疑惑,能夠經過GitHub聯繫我!

相關文章
相關標籤/搜索