1. 程式人生 > >淺談JVM記憶體模型

淺談JVM記憶體模型

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

                       

        執行引擎是Java虛擬機器最核心的組成部分之一。在Java虛擬機器規範中制定了虛擬機器位元組碼執行引擎的概念模型,這個概念模型稱為各種虛擬機器執行引擎的統一外觀。在不同的虛擬機器實現裡,執行引擎在執行Java程式碼時肯呢過有解釋執行和編譯執行兩種選擇。但從外觀看起來,所有的Java虛擬機器的執行引擎都是一致的:輸入的是位元組碼檔案,處理過程是位元組碼解析的等效過程,輸出的是執行結果。

1.程式計算器

       程式計算器(Program Counter Register)是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡,位元組碼直譯器工作時就是通過改變這個計算器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計算器來完成。

由於Java虛擬機器的多執行緒是通過執行緒輪流切換分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器都只會執行一條執行緒中的指令。因此,為了執行緒切換後能恢復到正確的位置,每條執行緒都需要有一個獨立的程式計算器,各條執行緒之間計算器互不影響,獨立儲存,稱這類記憶體區域為”執行緒私有“的記憶體。

如果記憶體正在執行一個Java方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令地址;如果正在執行的是Native方法。這個計數器值則為空(undefined)。此記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

2.Java虛擬機器棧

        Java虛擬機器棧(Java Virtual Machine Stacks)是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊。它主要用於存放物件引用

。其結構如圖(1)所示,棧與堆的關係如圖(2)所示。

                                         

                     圖(1)                                                                                                                圖(2)

        在Java虛擬機器規範中,對這個區域規定了兩種異常的情況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件,如果擴充套件時無法申請到足夠的記憶體,就會丟擲OutOfMemoryError異常。

2.1什麼是棧幀?

        棧幀(Stack Frame)是用於支援虛擬機器進行方法呼叫和方法執行的資料結構,它是虛擬機器執行時資料區中虛擬機器棧的棧元素。棧幀(Stack Frame)儲存了區域性變量表、運算元棧、動態連結、方法出口等資訊。每一個方法從呼叫直至執行完成的過程,都對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。

        每一個棧幀都包含了區域性變量表、運算元棧、動態連結、方法返回地址和一些額外的附加資訊。在編譯程式碼時,棧幀中需要多大的區域性變量表,多深的運算元棧有已經完全確定了,並寫入方法表的Code屬性中。因此,一個棧幀需要分配多少記憶體,不會受到程式執行期變數資料的影響,而取決於具體的虛擬機器實現。

        對於執行引擎來說,在活動執行緒中,只有位於棧頂的棧幀才是有效的,稱為當前棧幀(Current Stack Frame),與這個站幀相關聯的方法稱為當前方法(Current Method)。執行引擎執行的所有位元組碼指令都只針對當前棧幀進行操作,典型的棧幀結構如下圖所示:

                           

接下來詳細講解一下棧幀中的區域性變量表、運算元棧、動態連結、方法返回地址等各個部分的作用和資料結構。

區域性變量表:是一組變數值儲存空間,用於存放方法引數和方法內部定義的區域性變數。在Java程式編譯為Class檔案時,就在方法的Code屬性的max_locals資料項中確定了該方法所需要分配的區域性變量表的最大容量。

        區域性變量表的容量以變數槽(Variable Slot)為最小單位。每個Slot都應該能存放一個boolean,byte,char,short,int,float,reference或returnAddress型別的資料,這8種資料型別,都可以使用32位或更小的實體記憶體來存放,但這種描述與明確指出“每個Slot佔用32位長度的記憶體空間”是有一些差別的,它允許Slot的長度可以隨著處理器、作業系統或虛擬機器的不同而發生變化,只要保證即使在64位虛擬機器中使用了64位的實體記憶體空間去實現一個Slot,虛擬機器仍要使用對齊和補白的手段讓Slot在外觀看起來與32位虛擬機器中的一致。

        一個Slot可以存放一個32位以內的資料型別,Java中佔用32位以內的資料型別有boolean,byte,char,short,int,float,reference和returnAddress這8種類型。reference型別表示一個物件例項的引用,虛擬機器規範既沒有說明它的場地,也沒有明確指出這種引用是什麼結構。但從此引用中直接或間接地查詢到物件在Java堆中的資料存放的起始地址索引;此引用中直接或間接地查詢到物件所屬資料型別在方法區中的儲存的型別資訊。returnAddress型別目前很少見了,現在已經由異常表代替

        對於64位的資料型別,虛擬機器會以高位對齊的方式為其分配兩個連續的Slot空間。Java語言中64位的資料型別只有long和double由於區域性變量表建立線上程的堆疊上,是執行緒私有的資料,無論讀寫兩個連續的Slot是否為原子操作,都不會引起資料安全問題

        虛擬機器通過索引定位的方式使用區域性變量表,索引值的範圍是從0開始至區域性變量表最大的Slot數量。如果訪問的是32位資料型別的變數索引n就代表了使用第n個Slot,如果是64位資料型別的變數,則說明會同時使用n和n+1兩個Slot對於兩個相鄰的共同存放一個64位的資料的兩個Slot,不允許採用任何方式單獨訪問其中某一個,Java虛擬機器規範中明確要求瞭如果遇到進行這種操作的位元組碼序列,虛擬機器應該在類載入的校驗階段丟擲異常。

        在方法執行時,虛擬機器是使用區域性變量表完成引數值到引數變數列表的傳遞過程。如果執行的是例項方法,那區域性變量表中第0位索引的Slot預設是用於傳遞方法所屬物件例項的引用,在方法中可以通過關鍵字“this”來訪問這個隱含的引數,其餘引數則按照引數表順序排序,佔用從1開始的區域性變數Slot,引數分配完畢後,再根據方法體內部的變數順序和作用域分配其餘的Slot。

        為了儘可能節省棧幀空間,區域性變量表中的Slot是可以重用的。需要注意的是,一個區域性變數定義了但沒有賦值是不能使用的。

運算元棧:是一個後進先出的棧,它的最大深度在編譯的時候就寫入到Code屬性的max_stacks資料項中。運算元棧的每個元素可以是Java資料型別,包括long和double。32位資料型別所佔的棧容量為1,64為資料型別所佔的棧容量為2。在方法執行的任何時候,運算元棧的深度都不會超過在max_stacks資料項中設定的最大值。

       當一個方法剛開始執行的時候,這個方法的運算元棧是空的,在方法的執行過程中,會有各種位元組碼指令往運算元棧中寫入和提取內容,也即入棧/出棧操作

       運算元棧中的元素型別必須與位元組碼指令的序列嚴格匹配,在編譯程式程式碼時編譯器要嚴格保證這一點,在類校驗階段的資料流分析中還要再次驗證這一點。

       另外,在概念模型中,兩個棧幀作為虛擬機器棧的元素是完成相互獨立的,但在大多虛擬機器的實現裡都會做一些優化處理,令兩個棧幀出現一部分重疊。讓下面  棧幀的部分運算元棧與上面棧幀的部分區域性變量表重疊在一起,這樣在進行方法呼叫時就可以共用一部分資料,無須進行額外的引數傳遞,如圖所示:

                                                                

動態連結:每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連結(Dynamic Linking)。我們知道Class檔案的常量池中存有大量的符號引用,位元組碼中的方法呼叫指令就以常量池中指向方法的符號引用作為引數。這些符號引用一部分會在類載入階段或者第一次使 用的時候就轉化為直接引用,這種轉化成為靜態解析。另外一部分將在每一次執行期間轉化為直接引用,這部分成為動態連結。

方法返回地址:當一個方法開始執行後,只有兩種方式可以退出這個方法:第一種方式是執行引擎遇到任意一個方法返回的位元組碼指令,這時候肯呢過會有返回值傳遞給上層的方法呼叫者,是否有返回值和返回值的型別將根據遇到何種方法返回指令來決定,這種退出方法的方式稱為正常完成出口

        第二種是在方法執行過程中遇到了異常,並且這個異常沒有在方法體內得到處理,無論是Java虛擬機器內部產生的異常,還是程式碼中使用athrow位元組碼指令產生的異常,只要在本方法的異常表中沒有找到匹配的異常處理器,就會導致方法退出,這種退出方法的方式稱為異常完成出口一個方法使用異常完 成出口的方式退出,是不會給它的上層呼叫者產生任何返回值的。

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

3.本地方法棧

        本地方法棧(Native Method Stack)與虛擬機器棧所發揮的作用是非常相似的,他們之間的區別不過是虛擬機器棧為虛擬機器執行Java方法(也即是位元組碼)服務,而本地方法棧則為虛擬機器使用到的Native方法服務。有的虛擬機器(如HotSpot)直接把本地方法棧和虛擬機器棧合二為一。與虛擬機器棧一樣,本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。

4.Java堆

        對於大多數應用來說,Java堆(Java Heap)是Java虛擬機器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。

        Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱作“GC堆”(Garbage Collected Heap)。從記憶體回收的角度來看,由於現在收集器基本都是採用分代收集演算法,所以Java堆中還可以細分為:新生代和老年代;再細緻一點有:Eden空間,From Survivor空間,To Survivor空間等。從記憶體分配的角度來看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。不過無論如何劃分,都與存放內容無關,無論哪個區域,儲存的都是物件的例項,進一步劃分的目的是為了更好地回收記憶體,或更快地分配記憶體。

        Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可,就像我們的磁碟一樣。在實現時,既可以實現成固定大小的,也可以是可擴充套件的。如果堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

5.方法區

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

        在HotSpot虛擬機器上開發、部署程式的開發者來說,很多人都把方法區稱為“永久代”(Permanent Generation),本質上兩者並不等價,僅僅因為HotSpot虛擬機器的設計團隊選擇把GC分代收集擴充套件至方法區,或者說使用永久代實現方法區而已。

        Java虛擬機器規範對方法區的限制非常寬鬆,除了和Java堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴充套件外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域比較少出現,但並非資料進入了方法區就永久的存在。這區域的記憶體回收目標主要是針對常量池的回收和堆型別的解除安裝,一般來說,這個區域的回收情況比較難以令人滿意,尤其是型別的解除安裝,條件相當苛刻,但是這部分割槽域的回收確實是有必要的。

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

6.執行時常量池

        執行時常量池(Runtime Constant Pool)是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項是常量池(Constant Pool Table),用於存放編譯器生成的各種字面量和符號引用這部分內容將在類載入後進入方法區的執行時常量池中存放

        Java虛擬機器堆Class檔案每一部分的格式都有嚴格規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求才被虛擬機器認可、裝載和執行,但對於執行時常量池,Java虛擬機器卻沒有做任何細節的要求。不過,一般來說,除了儲存Class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存在執行時常量池中

        執行時常量池相對於Class檔案常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定要在編譯期才能產生,也就是並非預置入Class檔案中常量池的內容才能進入方法區的執行時常量池,執行期間也可能將新的常量放入到常量池中,這種特性被開發人員利用得比較多的是String類的intern()方法。

        當常量池無法再申請到記憶體時會丟擲OutOfMeMoryError異常。

7.直接記憶體

        直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域。但是這部分記憶體也被頻繁使用,而且也可能導致OutOfMemoryError異常出現。

        在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer物件作為這塊記憶體的引用進行操作。這樣能在一些場景中能顯著提高效能,因為避免了在Java堆和Native堆中來回複製資料。

        顯然,本地直接記憶體的分配不會受到Java堆大小的限制,但是,既然是記憶體,肯定還是會受到本地總記憶體大小以及處理器定址空間的限制。伺服器管理員在配置虛擬機器引數時,會根據實際記憶體設定-Xmx等引數資訊,但經常忽略直接記憶體,使得各個記憶體區域總和大於實體記憶體限制,從而導致動態擴充套件時出現OutOfMemoryError異常。

JDK8的記憶體模型

上面講述的是JDK8以前的Java記憶體模型,而JDK8中記憶體模型有所改變--方法區(Method Area)即程式設計師所熟知的永久代(Permanent Generation)變為元資料(MetaSpace)--物理空間,也就是我們電腦的記憶體。JDK8的記憶體模型如下圖所示:

                                     

相關推薦

JVM記憶體模型

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

JVM記憶體分配

JVM 記憶體分配   由於Java程式是交由JVM執行的,所以我們在談Java記憶體區域劃分的時候事實上是指JVM記憶體區域劃分。如下是具體java程式的執行過程:  首先Java原始碼檔案(.java字尾)會被Java編譯器編譯為位元組碼檔案(.class字尾),然後由JVM中的類

JVM】之記憶體模型

           java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。每個區域有自己建立和銷燬時間,根據《java虛擬機器規範》的規定,java虛擬機器所管理的

四十八、從JVM記憶體模型執行緒安全

作為一個三個多月沒有去工作的獨立開發者而言,今天去小米麵試了一把.怎麼說呢,無論你水平如何,請確保在面試之前要做準備,就像其中一位面試官說的一樣,我知道你水平不錯,但是無論如何也是要準備下的,不然你怎麼會連這個方法也忘記了? 此刻,我突然覺得我是一個假程式設計師.為什麼這麼說呢,作為一個從12年

JVM及原理

所有 memory 控制流 校驗 activit android 類信息 amp 不同 1、什麽是JVM ? JVM, 中文名是Java虛擬機, 正如它的名字, 是一個虛擬機器,來模擬通用的物理機。 JVM是一個標準,一套規範, 規定了.class文件在其內部運行的

JVM記憶體模型及GC回收機制的相關理解

在面試中我們經常會被問道關於JVM的面試問題。我們來整理下 這篇不錯 這個可以讓你恍然大悟 1 JAVA記憶體模型初體驗 JVM記憶體模型: 1 堆 :物件 2 棧(本地方法棧,虛擬機器棧):引數列表、基本資料型別 3 方法區(包括常量池):類變數、常量、程式碼段(code sege

JVM記憶體模型與垃圾回收

一、JVM體系結構 二、JVM Heap Memory 1.新生代(Young Generation)  - Eden Space  - Survivor FromSpace (S1)  - Survivor ToSpace (S2)

JVM記憶體模型、指令重排、記憶體屏障概念解析

 在高併發模型中,無是面對物理機SMP系統模型,還是面對像JVM的虛擬機器多執行緒併發記憶體模型,指令重排(編譯器、執行時)和記憶體屏障都是非常重要的概念,因此,搞清楚這些概念和原理很重要。否則,你很難搞清楚哪些操作是在併發執行緒中絕對安全的?哪些是相對安全的?哪些併發同步手段效能最低

Java記憶體區域

一、執行時資料區域 Java在執行時,會根據需要,將記憶體區按照如下區域劃分,分為多個部分: 其中,藍色為執行緒共享的記憶體區域,橙色為執行緒獨享的記憶體區域。 1.1、方法區 儲存了虛擬機器載入的類資訊、常量、靜態變數等資料 1.2、常量區 是方法區的一部分,常用於存放編譯期生成的

Java效能優化之JVM記憶體模型

JVM記憶體模型 首先介紹下Java程式具體執行的過程: Java原始碼檔案(.java字尾)會被Java編譯器編譯為位元組碼檔案(.class字尾); 由JVM中的類載入器載入各個類的位元組碼檔案,載入完畢之後,交由JVM執行引擎執行 在整個程式執行過程中,JVM會用==一段空間==來儲存程式執

[轉]JVM記憶體模型

最近排查一個線上java服務常駐記憶體異常高的問題,大概現象是:java堆Xmx配置了8G,但執行一段時間後常駐記憶體RES從5G逐漸增長到13G #補圖#,導致機器開始swap從而服務整體變慢。由於Xmx只配置了8G但RES常駐記憶體達到了13G,多出了5G堆外記憶體,經驗上判斷這裡超出太多不太正常。 前

JMM(JVM記憶體模型

Java通過多執行緒機制實現多工並行處理。 所有執行緒共享JVM記憶體(main memory),同時執行緒又有自己的工作記憶體。 當執行緒與主存進行互動時,資料從主存複製到工作記憶體中,由執行緒進行處理。 JVM的邏輯記憶體模型中,包含如下幾個部分: 1)程式計數器 2)虛擬機器棧 3

如何從程式設計的本質理解JVM記憶體模型

如何從程式設計的本質理解JVM記憶體模型 一般聊JVM記憶體模型都是把圖截出來,然後對著圖,解釋上面堆、棧之類的概念。這篇將分享下,如何從程式設計的本質上理解,JVM記憶體模型是什麼樣子,為什麼是這個樣子,不再死記硬背。 程式設計的本質 程式設計的本質是什麼,有這麼一句話,程式=演算法+資料結構。 這裡

目錄 1.1. JVM記憶體模型總體架構圖 1 1.2. JAVA堆 2 1.3. 方法區 元空間(Metaspace) 2 1.4. 虛擬機器棧 3 1.5. 本地方法區 4 2. 垃圾回收演算法 4 2

目錄 1.1. JVM記憶體模型總體架構圖 1 1.2. JAVA堆 2 1.3. 方法區 元空間(Metaspace) 2 1.4. 虛擬機器棧 3 1.5. 本地方法區 4 2. 垃圾回收演算法 4 2.1. 標記-清除演算法(Mark-Sweep) 4

速記JVM記憶體模型和垃圾回收策略

一、常用JVM引數 -Xms: 初始堆大小 -Xmx: 最大堆 -Xss: 棧容量 -PermSize: 方法區大小 -MaxPermSize: 最大方法區大小 -MaxDirectMemorySize: 最大直接記憶體大小 二、java虛擬機器基本結構   1.

JVM 記憶體模型概述(轉載)

摘要:   我們都知道,Java程式在執行前首先會被編譯成位元組碼檔案,然後再由Java虛擬機器執行這些位元組碼檔案從而使得Java程式得以執行。事實上,在程式執行過程中,記憶體的使用和管理一直是值得關注的問題。Java虛擬機器在執行Java程式的過程中會把它所管理的記

java String 比較問題及jvm記憶體模型

總是被java字串問題給困擾,今天總結一下: 看如下程式碼: public class Main{ public static void main(String[] args) { String i = "abc"; String j = "abc"; St

Android 記憶體溢位與記憶體洩漏

概念 記憶體溢位(Out of memory):系統會給每個APP分配記憶體,預設16M記憶體,每個手機廠商的預設值不一樣,當APP所需要的記憶體大於了系統分配的記憶體,就會造成記憶體溢位;記憶體溢位就是分配的記憶體被用光了,不夠用了。 記憶體洩漏(Memo

JVM記憶體模型

注意:java8中記憶體模型發生了變化,Metaspace替換了PermGen。 jvm記憶體結構大概分為: 序號 名稱 是否共享 作用 1 堆(head) 執行緒共享 所有的物件例項以及陣列都要在堆上分配,回收器主要管理的物件

JVM記憶體模型與調優

  https://blog.csdn.net/persistencegoing/article/details/84376427 圖片來源(https://blog.csdn.net/qq_22152261/article/details/79491536) JVM 記