深刻理解JVM(一)JVM內存模型

Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域,總共包括如下幾個運行時數據區域。數組

1 、程序計數器(Program Counter Register) 數據結構

程序計數器是一塊較小的內存空間,它的做用:多線程

1.1. 能夠看作是當前線程所執行的字節碼的信號指示器。字節碼解釋器就是經過改變該計數器的值來選取下一條須要執行的字節碼指令, 分支、循環、跳轉、異常處理、線程恢復等基礎功能都需依賴計數器來完成。注:可是,若是當前線程正在執行的是一個本地方法,那麼此時程序計數器爲空。eclipse

1.2. 在多線程的狀況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候可以知道該線程上次運行到哪兒了。函數

特色:線程私有的, 生命週期隨着線程的建立而建立,隨着線程的結束而死亡。 此內存區域是惟一一個在 Java 虛擬機規範中沒有規定任何 OutOfMemoryError 狀況的區域。優化

二、Java 虛擬機棧(Java Virtual Machine Stack)this

Java 虛擬機棧與程序計數器同樣,也是線程私有的,其生命週期與線程相同。虛擬機 棧描述的是 Java 方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法被調用 直至執行完成的過程就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。spa

局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、 float、long、double)、對象引用(reference 類型)和 returnAddress 類型(指向了一條字 節碼指令的地址)。其中 64 位長度的 long 和 double 會佔用 2 個局部變量空間(Slot,一個 32 位),其他數據類型只佔用 1 個。局部變量表所需的空間在編譯期間完成分配,當進入一 個方法時,其須要在幀中分配多大的局部變量空間是肯定的,方法運行期間不會改變局部變 量表的大小。局部變量表的建立是在方法被執行的時候,隨着棧幀的建立而建立。並且,局部變量表的大小在編譯時期就肯定下來了,在建立的時候只需分配事先規定好的大小便可。此外,在方法運行的過程當中局部變量表的大小是不會發生改變的。線程

Java 虛擬機規範中對該區域規定了兩種異常狀況:指針

2.1. 如 線 程 請 求 的 深 度 大 於 虛 擬 機 所 允 許 的 深 度 , 棧 溢 出 , 如 遞 歸 時 , 拋 出

StackOverflowError 異常。

2.2. 虛擬機棧動態擴展沒法申請到足夠的內存時,拋出 OutOfMemoryError 異常。

當方法傳遞參數時其實是一個方法將本身棧幀中局部變量表的副本傳遞給另外一個方法棧幀中的局 部變量表(注意是副本,而不是其自己),無論數據類型是什麼(基本類型,引用類型)

三、本地方法棧(Native Method Stack)

Java 虛擬機可能會使用到傳統的棧來支持 native 方法(使用 Java 語言之外的其它語言 編寫的方法)的執行。線程私有的,如 Sun HotSpot 虛擬機直接把本地方法棧和虛擬機棧合 二爲一。Java 虛擬機規範中對該區域規定了兩種異常狀況:

3.1.如線程請求的深度大於虛擬機所容許的深度,拋出 StackOverflowError 異常。

3.2.虛擬機棧動態擴展沒法申請到足夠的內存時,拋出 OutOfMemoryError 異常。

四、Java 堆(Java Heap)

Java 堆是 Java 虛擬機管理內存中最大的一塊,是全部線程共享的內存區域,隨虛擬機的啓動而建立。該區域惟一目的是存放對象實例,幾乎全部對象的實例都在堆裏面分配。 Java 虛擬機規範規定,Java 堆能夠出於物理上不連續的內存空間中,只要邏輯上連續便可,如同磁盤空間同樣,既能夠實現成固定大小,也能夠是擴展的,當前主流虛擬機都是 按照擴展來實現的(經過-Xmx 和-Xms 控制)。

Java堆是垃圾收集器管理的主要區域,所以也叫"GC堆",細分一點能夠分爲新生代和老年代;再細緻一點新生代能夠分爲Eden空間、From Survivor空間、ToSurvivor空間。

Java 虛擬機規範中對該區域規定了 OutOfMemoryError 異常:若是堆中沒有 內存完成實例分配,而且堆沒法再擴展則拋出 OutOfMemoryError 異常。(當 Ol d 區被放滿的以後,進行 Full GC,Full GC 後,若 Survivor 及 old 區仍然沒法存放 從 Eden 複製過來的部分對象,則出現 OOM 錯誤/或者直接存放大對象、大數組,致使老年代空間不足)

五、方法區(Method Area)

方法區與 Java 堆同樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信 息、常量、靜態變量、即時編譯器編譯後的代碼等數據。在 HotSpot 中用永久代來實現方法區,而其餘虛擬機(如 BEA JRockit、IBM J9 等)是不存在永久代的。

Java7 中已經將運行時常量池從永久代移除,在 Java 堆(Heap)中開闢了一塊區域存放運行時常量池。而在 Java8 中,已經完全沒有了永久代,將方法區直接放在一個與堆不相連的本地內存區域,這個區域被叫作元空間。

元空間的本質和永久代相似,都是對 JVM 規範中方法區的實現。不過元空間與永久代 之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元 空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小:-XX:MetaspaceSize,初始空間大小。-XX:MaxMetaspaceSize,最大空間,默認是沒有 限制的。Java 虛擬機規範中對方法區規定了 OutOfMemoryError 異常: 若是方法區的內存空間 不能知足內存分配請求,那 Java 虛擬機將拋出一個 OutOfMemoryError 異常。

六、運行時常量池(Runtime Constant Pool)

運行時常量池是方法區的一部分。線程共享。Class 文件中除了有類的版本、字段、方 法、接口等信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面常量和符號引 用,這部份內容在類加載後存放到方法區的常量池中。

static 修飾的靜態變量也存放在方法區中,但不是在常量池中(不能修飾局部變量),不 能在一個方法內部定義 static 變量(final 能夠),只能定義爲成員變量。

當這個類被Java虛擬機加載後,class文件中的常量就存放在方法區的運行時常量池中。並且在運行期間,能夠向常量池中添加新的常量。如:String類的intern()方法就能在運行期間向常量池中添加字符串常量。

當運行時常量池中的某些常量沒有被對象引用,同時也沒有被變量引用,那麼就須要垃圾收集器回收。

Java 虛擬機規範中對該區域規定了 OutOfMemoryError 異常: 當常量池沒法申請到內 存時拋出 OutOfMemoryError 異常。

七、直接內存

直接內存是除Java虛擬機以外的內存,但也有可能被Java使用。

在NIO中引入了一種基於通道和緩衝的IO方式。它能夠經過調用本地方法直接分配Java虛擬機以外的內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象直接操做該內存,而無需先將外面內存中的數據複製到堆中再操做,從而提高了數據操做的效率。

直接內存的大小不受Java虛擬機控制,但既然是內存,當內存不足時就會拋出OOM異常。

八、棧幀

棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區 的虛擬機棧的棧元素。棧幀存儲了方法的局部變量表,操做數棧,動態鏈接和方法返回地址 等信息。第一個方法從調用開始到執行完成,就對應着一個棧幀在虛擬機棧中從入棧到出棧 的過程。在編譯代碼的時候,棧幀中須要多大的局部變量表,多深的操做數棧都已經徹底確 定了,而且寫入到了方法表的 Code 屬性中,所以一個棧幀須要分配多少內存,不會受到程 序運行期變量數據的影響,而僅僅取決於具體虛擬機的實現。一個線程中的方法調用鏈可能會很長,不少方法都同時處理執行狀態。對於執行引擎來 講,活動線程中,只有虛擬機棧頂的棧幀纔是有效的,稱爲當前棧幀(Current Stack Frame),這個棧幀所關聯的方法稱爲當前方法(Current Method)。

8.一、局部變量表

局部標量表是一組變量值的存儲空間,一個以字長爲單位,從 0 開始計數的數組,用於 存放方法參數和局部變量。變量槽 (Variable Slot)是局部變量表的最小單位,沒有強制規 定大小爲 32 位,雖然 32 位足夠存放大部分類型的數據。一個 Slot 能夠存放 boolean、 byte、char、short、int、float、reference 和 returnAddress 8 種類型。其中 reference 表 示對一個對象實例的引用。returnAddress 則指向了一條字節碼指令的地址。 對於 64 位的 long 和 double 變量而言,虛擬機會爲其分配兩個連續的 Slot 空間。虛擬機經過索引定位的方式使用局部變量表。以前咱們知道,局部變量表存放的是方法參數和局部變量。當調用方法是非 static 方法時,局部變量表中第 0 位索引的 Slot 默認是用於傳遞方法所屬對象實例的引用,即「this」關鍵字指向的對象。分配完方法參數後,便會 依次分配方法內部定義的局部變量。

爲了節省棧幀空間,局部變量表中的 Slot 是能夠重用的。當離開了某些變量的做用域 以後,這些變量對應的 Slot 就能夠交給其餘變量使用。

8.二、操做數棧

操做數棧被組織成一個以字長爲單位的數組。但不是經過索引來訪問,而是經過標準棧 操做--壓棧和出棧來訪問。方法執行中進行算術運算或者是調用其餘的方法進行參數傳遞的 時候是經過操做數棧進行的。

在概念模型中,兩個棧幀是相互獨立的。可是大多數虛擬機的實現都會進行優化,令兩 個棧幀出現一部分重疊。令下面的部分操做數棧與上面的局部變量表重疊在一塊,這樣在方 法調用的時候能夠共用一部分數據,無需進行額外的參數複製傳遞。

8.三、幀數據區

棧幀須要一些數據來支持常量池解析、正常方法返回和異常處理等。在幀數據區中保存

着訪問常量池的指針,方便程序訪問常量池。此外,當函數返回或者出現異常時,虛擬機必 須恢復調用者函數的棧幀,並讓調用者函數繼續執行下去。對於異常處理,虛擬機必須有一 個異常處理表,方便在發生異常的時候找處處理異常的代碼,所以異常處理表也是幀數據區 中重要的一部分。

8.3.1 動態鏈接

每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了

支持方法調用過程當中的動態鏈接。符號引用一部分會在類加載階段或者第一次使用的時候就 轉化爲直接引用,這種轉化成爲靜態解析。另一部分在每一次運行期間轉化爲直接引用, 這部分稱爲動態鏈接。

8.3.2 方法返回地址

當一個方法開始執行後,只有兩種方式能夠退出這個方法。第一種是執行引擎遇到任意 一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者。

另外一種退出方式是,在方法執行過程當中遇到了異常,而且這個異常沒有在方法體內獲得 處理。不管採用何種退出方式,在方法退出以後,都須要返回到方法被調用的位置,程序才 能繼續執行。

8.4 OutOfMemoryError

在 eclipse 中設置-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError(堆最小 值、最大值設置成同樣爲了不自動擴展,輸出內存溢出時信息)

Java 堆用於存儲對象實例,只要不斷建立對象,而且保證 GC Roots 到對象之間有可達路 徑來避免垃圾回收,當對象數量達到最大堆容量後就產生內存溢出異常。

相關文章
相關標籤/搜索