Java 虛擬機器之三:Java虛擬機器的記憶體結構

百聯達發表於2018-09-02

一:簡介

記憶體(Memory)也被稱為記憶體儲器,其作用是用於暫時存放CPU中的運算資料,以及與硬碟等外部儲存器交換的資料。只要計算機在執行中,CPU就會把需要運算的資料調到記憶體中進行運算,當運算完成後CPU再將結果傳送出來。

Java虛擬機器在執行Java程式過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程式的啟動而存在,有些區域則依賴使用者執行緒的啟動和結束而建立和銷燬。

程式: 一段程式的執行過程,是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是作業系統動態執行的基本單元,在傳統的作業系統中,程式既是基本的分配單元,也是基本的執行單元。

程式是一個實體。每一個程式都有它自己的地址空間,一般情況下,包括文字區域(text region)、資料區域(data region)和堆疊(stack region)。文字區域儲存處理器執行的程式碼;資料區域儲存變數和程式執行期間使用的動態分配的記憶體;堆疊區域儲存著活動過程呼叫的指令和本地變數。第二,程式是一個“執行中的程式”。程式是一個沒有生命的實體,只有處理器賦予程式生命時,它才能成為一個活動的實體,我們稱其為程式。

程式有三個狀態,就緒、執行和阻塞。就緒狀態其實就是獲取了出cpu外的所有資源,只要處理器分配資源就可以馬上執行。就緒狀態有排隊序列什麼的,排隊原則不再贅述。執行態就是獲得了處理器分配的資源,程式開始執行。阻塞態,當程式條件不夠時候,需要等待條件滿足時候才能執行,如等待i/o操作時候,此刻的狀態就叫阻塞態。

執行緒: 通常在一個程式中可以包含若干個執行緒,當然一個程式中至少有一個執行緒,不然沒有存在的意義。執行緒可以利用程式所擁有的資源,在引入執行緒的作業系統中,通常都是把程式作為分配資源的基本單位,而把執行緒作為獨立執行和獨立排程的基本單位,由於執行緒比程式更小,基本上不擁有系統資源,故對它的排程所付出的開銷就會小得多,能更高效的提高系統多個程式間併發執行的程度。

程式和執行緒的主要差別: 在於它們是不同的作業系統資源管理方式。程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其它程式產生影響,而執行緒只是一個程式中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程式死掉,所以多程式的程式要比多執行緒的程式健壯,但在程式切換時,耗費資源較大,效率要差一些

二:程式計數器

  1. 程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒執行的位元組碼行號的指示器。

  2. 位元組碼直譯器工作時就是透過改變這個計數器的值來選取下一條需要執行的位元組碼指令;分支,迴圈,跳轉,異常處理,執行緒恢復等基礎功能都需要依賴這個計數器來完成

  3. 當執行緒獲得時間片處於執行過程時,CPU會按照程式計數器中儲存的內容依次的取出指令執行,當CPU取出當前指令時,CPU自動修改程式計數器內容,使其指向下一條指令位元組碼行號。由於程式計數器中儲存的是數字值,因此不會隨著程式執行而擴大需求空間,故不會發生溢位。

  4. 假設執行緒A正在執行,當執行到某個階段,優先順序更高執行緒B執行。此時,執行緒A掛起,執行緒B執行。當執行緒B執行完畢後,需要喚醒執行緒A繼續執行,那麼如何從執行緒A的中斷位置繼續執行呢,那就需要CPU訪問執行緒A的程式計數器,從中獲取下一條執行指令,保證程式繼續執行。由於每個執行緒需要儲存自身的執行位置,也就使得程式計數器為執行緒私有。

  5. native本地方法大多是透過C實現並未編譯成需要執行的位元組碼指令,所以在計數器中是undefined

  6. 這個記憶體區域是 唯一一個在java虛擬界規範中沒有規定任何OutOfMemoryError的情況的區域

三:Java虛擬機器棧

  1. 它是一個後入先出的棧,其中儲存的元素是棧幀 。它也是執行緒私有的,它的生命週期與執行緒相同。

  2. 程式執行時,每呼叫一個方法就會生成一個棧幀,同時將當前正在執行方法的棧幀壓入虛擬機器棧。虛擬機器棧頂的棧幀為“當前活躍棧幀”。既然棧幀是呼叫方法時建立,那麼其儲存內容必然與方法息息相關。


區域性變數表: 區域性變數表是一組區域性變數值儲存空間,用於存放方法引數和方法內部定義的區域性變數。區域性變數表存放了編譯器可知的各種基本資料型別,物件引用和returnAddress型別。區域性變數表的記憶體是在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變數表的大小。

運算元棧: 運算元棧是一個以字長為單位的陣列。但它不是透過索引來訪問,而是透過標準的棧操作—壓棧和出棧—來訪問的。比如,如果某個指令A把一個值1壓入到運算元棧中,指令B將值2壓入棧中,稍後指令C就可以彈出這兩個個值來進行相加計算,並將結果3壓入棧中。透過運算元棧可以完成方法中的一些運算。

動態連結: 每個棧幀內部都包含一個指向當前方法所在型別的執行時常量池的引用,以便對當前方法的程式碼實現動態連結。在class檔案裡面,一個方法如果要呼叫另外一個方法,或者訪問其成員變數,則需要透過符號引用來表示,那麼動態連結的作用就是在恰當的時候將這些以符號引用所表示的方法或是變數解析成直接引用。

返回地址: 一般來說,方法正常退出時,呼叫者PC計數器的值就可以作為返回地址,棧幀中很可能會儲存這個計數器值。而方法異常退出時,返回地址是要透過異常處理器來確定的,棧幀中一般不會儲存這部分資訊。 方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的區域性變數表和運算元棧,把返回值(如果有的話)壓入呼叫棧幀的運算元棧中,呼叫PC計數器的值以指向方法呼叫指令後面的一條指令等。

其他資訊: 虛擬機器規範允許具體的虛擬機器實現增加一些規範裡沒有描述的資訊到棧幀中,例如與高度相關的資訊,這部分資訊完全取決於具體的虛擬機器實現。

3.如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常; 如果虛擬機器棧可以動態擴充套件,如果擴充套件時無法申請到足夠的記憶體,就會丟擲OutOfMemoryError異常。

四:本地方法棧

  1. 本地方法棧與虛擬機器棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機器棧是為虛擬機器執行Java方法服務,而本地方法棧則為虛擬機器使用到的Native方法服務。

  2. 本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。

五:Java堆

  1. Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。

  2. Java堆一般分為三大部分:新生代,老年代和永久代

    新生代: 主要是用來存放新生的物件。一般佔據堆的1/3空間。由於頻繁建立物件,所以新生代會頻繁觸發MinorGC進行垃圾回收。

    老年代: 主要存放應用程式中生命週期長的記憶體物件。老年代的物件比較穩定,所以MajorGC不會頻繁執行。在進行MajorGC前一般都先進行了一次MinorGC,使得有新生代的物件晉身入老年代,導致空間不夠用時才觸發。當無法找到足夠大的連續空間分配給新建立的較大物件時也會提前觸發一次MajorGC進行垃圾回收騰出空間。

    永久代: 指記憶體的永久儲存區域,主要存放Class和Meta(後設資料)的資訊,Class在被載入的時候被放入永久區域. 它和和存放例項的區域不同,GC不會在主程式執行期對永久區域進行清理。所以這也導致了永久代的區域會隨著載入的Class的增多而脹滿,最終丟擲OOM異常。

  3. Java堆是垃圾收集器管理的主要區域。

  4. Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。

  5. 可以透過-Xmx和-Xms來擴充套件,如果無法再擴充套件時,將會丟擲OutOfMemoryError異常

六:方法區

  1. 方法區與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料。

  2. 當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28624388/viewspace-2213406/,如需轉載,請註明出處,否則將追究法律責任。

相關文章