JVM的記憶體區域劃分

2022-09-12 12:27:11 字數 3441 閱讀 3307

jvm的記憶體區域劃分

在j**a語言當中,記憶體是如何劃分的呢?

由於j**a程式是交由jvm執行的,所以我們在談j**a記憶體區域劃分的時候事實上是指jvm記憶體區域劃分。在討論jvm記憶體區域劃分之前,先來看一下j**a程式具體執行的過程:

如上圖所示,首先j**a源**檔案(.j**a字尾)會被j**a編譯器編譯為位元組碼檔案(.class字尾),然後由jvm中的類載入器載入各個類的位元組碼檔案,載入完畢之後,交由jvm執行引擎執行。在整個程式執行過程中,jvm會用一段空間來儲存程式執行期間需要用到的資料和相關資訊,這段空間一般被稱作為runtime data area(執行時資料區),也就是我們常說的jvm記憶體。因此,在j**a中我們常常說到的記憶體管理就是針對這段空間進行管理(如何分配和**記憶體空間)。

在知道了jvm記憶體是什麼東西之後,下面我們就來討論一下這段空間具體是如何劃分區域的,是不是也像c語言中一樣也存在棧和堆呢?

根據《j**a虛擬機器規範》的規定,執行時資料區通常包括這幾個部分:程式計數器(program counter register)、j**a棧(vm stack)、本地方法棧(native method stack)、方法區(method area)、堆(heap)。

如上圖所示,jvm中的執行時資料區應該包括這些部分。在jvm規範中雖然規定了程式在執行期間執行時資料區應該包括這幾部分,但是至於具體如何實現並沒有做出規定,不同的虛擬機器廠商可以有不同的實現方式。

下面我們來了解一下執行時資料區的每部分具體用來儲存程式執行過程中的哪些資料。

程式計數器(program counter register),也有稱作為pc暫存器。想必學過組合語言的朋友對程式計數器這個概念並不陌生,在組合語言中,程式計數器是指cpu中的暫存器,它儲存的是程式當前執行的指令的位址(也可以說儲存下一條指令的所在儲存單元的位址),當cpu需要執行指令時,需要從程式計數器中得到當前需要執行的指令所在儲存單元的位址,然後根據得到的位址獲取到指令,在得到指令之後,程式計數器便自動加1或者根據轉移指標得到下一條指令的位址,如此迴圈,直至執行完所有的指令。

雖然jvm中的程式計數器並不像組合語言中的程式計數器一樣是物理概念上的cpu暫存器,但是jvm中的程式計數器的功能跟組合語言中的程式計數器的功能在邏輯上是等同的,也就是說是用來指示 執行哪條指令的。

在jvm規範中規定,如果執行緒執行的是非native方法,則程式計數器中儲存的是當前需要執行的指令的位址;如果執行緒執行的是native方法,則程式計數器中的值是undefined。

由於程式計數器中儲存的資料所佔空間的大小不會隨程式的執行而發生改變,因此,對於程式計數器是不會發生記憶體溢位現象(outofmemory)的。

j**a棧也稱作虛擬機器棧(j**a vitual machine stack),也就是我們常常所說的棧,跟c語言的資料段中的棧類似。事實上,j**a棧是j**a方法執行的記憶體模型。為什麼這麼說呢?下面就來解釋一下其中的原因。

j**a棧中存放的是乙個個的棧幀,每個棧幀對應乙個被呼叫的方法,在棧幀中包括區域性變數表(local variables)、運算元棧(operand stack)、指向當前方法所屬的類的執行時常量池(執行時常量池的概念在方法區部分會談到)的引用(reference to runtime constant pool)、方法返回位址(return address)和一些額外的附加資訊。當執行緒執行乙個方法時,就會隨之建立乙個對應的棧幀,並將建立的棧幀壓棧。當方法執行完畢之後,便會將棧幀出棧。因此可知,執行緒當前執行的方法所對應的棧幀必定位於j**a棧的頂部。講到這裡,大家就應該會明白為什麼 在 使用 遞迴方法的時候容易導致棧記憶體溢位的現象了以及為什麼棧區的空間不用程式設計師去管理了(當然在j**a中,程式設計師基本不用關係到記憶體分配和釋放的事情,因為j**a有自己的垃圾**機制),這部分空間的分配和釋放都是由系統自動實施的。對於所有的程式語言來說,棧這部分空間對程式設計師來說是不透明的。下圖表示了乙個j**a棧的模型:

區域性變數表,顧名思義,想必不用解釋大家應該明白它的作用了吧。就是用來儲存方法中的區域性變數(包括在方法中宣告的非靜態變數以及函式形參)。對於基本資料型別的變數,則直接儲存它的值,對於引用型別的變數,則存的是指向物件的引用。區域性變數表的大小在編譯器就可以確定其大小了,因此在程式執行期間區域性變數表的大小是不會改變的。

運算元棧,想必學過資料結構中的棧的朋友想必對表示式求值問題不會陌生,棧最典型的乙個應用就是用來對表示式求值。想想乙個執行緒執行方法的過程中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。因此可以這麼說,程式中的所有計算過程都是在借助於運算元棧來完成的。

指向執行時常量池的引用,因為在方法執行的過程中有可能需要用到類中的常量,所以必須要有乙個引用指向執行時常量。

方法返回位址,當乙個方法執行完畢之後,要返回之前呼叫它的地方,因此在棧幀中必須儲存乙個方法返回位址。

由於每個執行緒正在執行的方法可能不同,因此每個執行緒都會有乙個自己的j**a棧,互不干擾。

本地方法棧與j**a棧的作用和原理非常相似。區別只不過是j**a棧是為執行j**a方法服務的,而本地方法棧則是為執行本地方法(native method)服務的。在jvm規範中,並沒有對本地方發展的具體實現方法以及資料結構作強制規定,虛擬機器可以自由實現它。在hotsopt虛擬機器中直接就把本地方法棧和j**a棧合二為一。

在c語言中,堆這部分空間是唯一乙個程式設計師可以管理的記憶體區域。程式設計師可以通過malloc函式和free函式在堆上申請和釋放空間。那麼在j**a中是怎麼樣的呢?

j**a中的堆是用來儲存物件本身的以及陣列(當然,陣列引用是存放在j**a棧中的)。只不過和c語言中的不同,在j**a中,程式設計師基本不用去關心空間釋放的問題,j**a的垃圾**機制會自動進行處理。因此這部分空間也是j**a垃圾收集器管理的主要區域。另外,堆是被所有執行緒共享的,在jvm中只有乙個堆。

方法區在jvm中也是乙個非常重要的區域,它與堆一樣,是被執行緒共享的區域。在方法區中,儲存了每個類的資訊(包括類的名稱、方法資訊、字段資訊)、靜態變數、常量以及編譯器編譯後的**等。

在class檔案中除了類的字段、方法、介面等描述資訊外,還有一項資訊是常量池,用來儲存編譯期間生成的字面量和符號引用。

在方法區中有乙個非常重要的部分就是執行時常量池,它是每乙個類或介面的常量池的執行時表示形式,在類和介面被載入到jvm後,對應的執行時常量池就被建立出來。當然並非class檔案常量池中的內容才能進入執行時常量池,在執行期間也可將新的常量放入執行時常量池中,比如string的intern方法。

在jvm規範中,沒有強制要求方法區必須實現垃圾**。很多人習慣將方法區稱為「永久代」,是因為hotspot虛擬機器以永久代來實現方法區,從而jvm的垃圾收集器可以像管理堆區一樣管理這部分區域,從而不需要專門為這部分設計垃圾**機制。不過自從jdk7之後,hotspot虛擬機器便將執行時常量池從永久代移除了。

JVM記憶體劃分

從上圖可以看出,jvm記憶體區域可以簡單的劃分為方法區,堆區,虛擬機器棧,本地方法棧和程式計數器。上圖中,淺色的為執行緒共有的記憶體區域,深色的為執行緒私有的記憶體區域。可以看出,方法區和堆區是所有執行緒之間共享的記憶體區,而棧區和pc則是執行緒本身私有的,不能被其他執行緒所共享。我們簡單的說下各個...

二。JVM記憶體區域

jdk1.8之前,代表jvm內一塊區域。jdk1.8之後,也就是元空間metaspace。存放類,靜態變數,常量池。總之與類有關的都在方法區。類一般先載入進方法區。寫好的 被翻譯成位元組碼,對應各種位元組碼指令。計數器就是記錄當前位元組碼指令的位置。jvm多執行緒併發操作時,每個執行緒都有自己的計數...

JVM(二)記憶體區域

堆總被分為兩個部分 新生代和老年代,其中新生代中又被分為eden區和survivor區,survivor區由form survivor和to survivor組成 具體的gc 物件分配方面會在後面的文章講到 在棧幀中一般來說兩個棧幀是不會相互有關係的,都是獨立存在的。但是在某些情況下,會使兩個獨立的...