jvm內存模型

Java和C++之間有一堵內存動態分配和垃圾收集技術所圍成的“高牆”,牆外面的人想進去,牆裏面的人卻想出來。

運行時數據區域

java虛擬機在執行程序的過程中會把所管理的內存劃分爲若干個不同的數 據區域。這些區域各自有自個的用途,以及創建和銷燬時間。有的區域生命週期和線程同步,有的則是和虛擬機同步。
運行時數據區


程序計數器

程序計數器是一塊較小的內存區域,它可以看作當前線程的所執行的字節碼行號指示器。在虛擬機概念模型裏面,虛擬機通過改變這個計數器的值來選取下一條需要執行的字節碼指令。同時它是線程私有的。
需要注意的是,如果線程執行的是native方法,那麼這個計數器的值應該爲0。同時,它還是虛擬機規範中唯一不會發生OutOfMemoryError的區域。

java虛擬機棧

線程私有,它的生命週期與線程相同。java虛擬機棧描述的是java方法執行的內存模型:每個方法在執行的同時會創建一個棧幀用來存儲局部變量表、操作數棧、動態鏈接、方法出入口的信息。一個方法的執行到結束就對應着棧幀在虛擬機棧中的入棧和出棧。
對於執行引擎來說,活動線程中,只有棧頂的棧幀是有效的,稱爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法。執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。
局部變量表
局部變量表用來存放方法的參數和方法中定義的局部變量。局部變量表達大小在編譯成class文件的時候就確定了,在方法的Code屬性中max_locals確定了局部變量的最大容量。
局部變量局部的容量以變量槽(Variable Slot)爲最小單位。虛擬機規範中並沒有規定一個slot所佔用的內存空間,只是說每個slot應該存放一個char,boolean,byte,short,float,reference,int,returnAddress。64位的數據類型long和double需要兩個連續的slot來存放。
局部變量表是用基於索引的方式來訪問的。索引返回從0到局部變量的最大數。
在方法的執行過程中,使用局部變量表完成從參數值到參數列表的傳遞過程。比如,如果執行的是實例方法(不是static方法),那麼在默認的情況下局部變量表索引爲0的位置存放的是當前調用方法的實例對象,即this。同時,爲了節省棧局部變量表中已經被佔用的slot可以會被覆蓋,因爲,如果超出了局部變量的作用於範圍,那麼該局部變量所佔用的slot就可以被覆蓋。但是,Slot對對象的引用會影響GC(要是被引用,將不會被回收)。

操作數棧
操作數棧和局部變量表一樣。不過操作數棧的訪問是基於標準的棧操作(入棧和出棧)。
在方法執行的過程中字節碼指令往往會從操作數棧中寫入和提取數據。比如說要執行兩個int型的數據相加,就需要從操作數棧的棧頂彈出兩個int型的數據然後相加,把相加之後的結果壓入棧中。下面演示虛擬機是如何將兩個int類型的數據相加的。

虛擬機指令:

begin  
iload_0    // push the int in local variable 0 ontothe stack  
iload_1    //push the int in local variable 1 onto the stack  
iadd       // pop two ints, add them, push result  
istore_2   // pop int, store into local variable 2  
end  

圖片描述:
這裏寫圖片描述

1.執行iload_0,將局部變量表索引爲0位置的int型數據壓入操作數棧中;
2.執行iload_1 ,將局部變量表索引爲1位置的int型數據壓入操作數棧中;
3.執行iadd,將彈出操作數棧棧頂的連個int類型數據然後相加,把相加的結果壓入棧頂;
4.執行istore_2,將操作數棧棧頂的int類型數據存儲到局部變量表索引爲2的位置處。

動態連接
每一個棧幀中都存放着一個指向運行時常量池的符號引用。在類加載的解析階段將符號引用轉換爲直接引用叫做靜態解析,在運行階段將符號引用轉換爲直接引用叫做動態鏈接。

返回值地址
每個返回執行完成都有兩種形式。第一種是正常執行完成,第二種是在方法的執行的過程中發生了異常,同時方法中又不存在對應的異常處理器。不管方法以何種方式接觸,方法都需要在執行完成之後會到方法調用的位置,程序才能正常執行,方法返回時可能在棧幀中保存一些信息來幫助恢復上層方法的執行狀態。如果方法是正常退出的,則調用者的PC計數器的值就可以作爲返回地址,,果是因爲異常退出的,則是需要通過異常處理表來確定。

異常
java虛擬機規範中規定,虛擬機棧存在兩種異常:
1.如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError 異常。
2.一般而言虛擬機的棧的深度允許動態擴展的,但是當擴展之後還是無法申請到足夠的內存就會發生OutOfMemoryError異常。

本地方法棧

本地方法棧(Native MethodStacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java 方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native 方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot 虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。
與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。

是java虛擬機內存中佔用最大的一塊區域,幾乎所有的java對象的內存分配都是在這裏進行的。它是所有線程共享的一個區域。
同時它也是垃圾的最主要的區域,所以它也可以叫做GC堆。由於現在的垃圾收集器都是基於分代算法實現的。所以更加細緻的劃分堆區可以劃分爲新生代和老年代。新生代又可以劃分爲Eden,from survivor,to survivor。
根據虛擬機規範的規定,java堆可以處於物理上不連續的內存空間。只要邏輯上面連續即可,就像我們的磁盤空間一樣。
堆的大小可以通過-Xms(最小值)和-Xmx(最大值)參數設置,-Xms爲JVM啓動時申請的最小內存,默認爲操作系統物理內存的1/64但小於1G,
-Xmx爲JVM可申請的最大內存,默認爲物理內存的1/4但小於1G,默認當空餘堆內存小於40%時,
JVM會增大Heap到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當空餘堆內存大於70%時,
JVM會減小heap的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,對於運行系統,
爲避免在運行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣。

方法區

主要是用來存儲類信息,常量和靜態變量的。
對於在HotSpot上面開發的人來說,更加習慣上把方法區稱作“永久代”。但是方法區並不等於永久代,只是HotSpot的設計團隊把垃圾回收擴展到方法區了。方法區除了和堆區一樣可以使用不許連續的物理內存之外,還可以不實現垃圾回收。但是該區域的垃圾回收是必要的,它的主要工作是常量的回收和類的卸載。
由於所有的線程都共享方法區,所以,方法區裏的數據訪問必須被設計成線程安全的。例如,假如同時有兩個線程都企圖訪問方法區中的同一個類,而這個類還沒有被裝入JVM,那麼只允許一個線程去裝載它,而其它線程必須等待。
根據java虛擬機規範規定,當方法區無法滿足內存分配需求的時候,就會拋出OutOfMemoryError異常。

運行時常量池
運行時常量池是方法區中的一部分。編譯的時候,將各種字面量和符號引用存放在Class文件的常量池中,而運行常量池主要是將Class文件中的常量池在類加載的過程加載進來。
同時運行時常量池相對於Class文件中的常量池中還具備一個重要的特性,那就是可以動態擴展。
因爲運行時常量池是方法區的一部分,自然而然受到方法去的內存限制。當無法申請到內存的時候就會拋出OutOfMemoryError異常。

我的理解
在剛剛開始學習jvm內存劃分的時候我是這樣理解的:就將jvm內存劃分爲棧和堆,棧用來存放局部變量和引用,堆區是用來創建對象的,所有的對象都在這裏創建,方法區是堆區的一部分用來存放常量。

直接內存

直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError 異常出現,所以我們放到這裏一起講解。
在JDK 1.4 中新加入了NIO(NewInput/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O 方式,它可以使用Native 函數庫直接分配堆外內存,然後通過一個存儲在Java 堆裏面的DirectByteBuffer 對象作爲這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因爲避免了在Java堆和Native堆中來回複製數據。

對象的訪問

邏輯內存模型我們已經看到了,那當我們建立一個對象的時候是怎麼進行訪問的呢?
在Java 語言中,對象訪問是如何進行的?對象訪問在Java 語言中無處不在,是最普通的程序行爲,但即使是最簡單的訪問,也會卻涉及Java 棧、Java 堆、方法區這三個最重要內存區。
以下面這段代碼爲例進行分析

Object obj = new Object();

這段代碼看似簡單,卻牽涉到了java虛擬機棧,堆區和方法區三個重要的區域。
1、首先Object obj會在java虛擬機棧的當前幀中創建obj的局部變量,類型爲reference;
2、當遇到new這個關鍵詞的時候,虛擬機會檢查虛擬機中對對應的類型信息是否已經加載。如果沒有加載,就先加載這個類。本例中會檢查Object類信息是否被加載。
3、確認類信息被加載之後虛擬機會爲對象分配內存空間(這裏分配的是實例數據的內存空間)。
4、接下來是將obj變量指向堆中創建的Object對象的實例,同時通過obj的引用應該還可以找對Object類在方法區中對應的類型信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章