1. 程式人生 > >jvm學習(一)之記憶體模型區域詳解

jvm學習(一)之記憶體模型區域詳解

1,引言     在記憶體管理區域java與c、c++語言不同的是jvm負責管理記憶體控制和垃圾回收的功能,而c、c++程式設計師需要程式碼裡面管理記憶體,這樣雖然方便了java的開發,但出現記憶體溢位和洩漏等問題也不好排查,讓我們先了解下虛擬機器記憶體模型。      2,jvm記憶體模型圖

這裡寫圖片描述

3,程式計數器     官方解釋是程式計數器是當前執行緒所執行的位元組碼的行號指示器,在虛擬機器的概念模型裡(僅是概念模型,各種虛擬機器可能會通過一些更高效的方式去實現),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,如:分支、迴圈、跳轉、異常處理、執行緒恢復(多執行緒切換)等基礎功能。     這樣看起來比較抽象,其實可以這樣理解,多執行緒是通過執行緒輪流切換來獲得CPU執行時間的,因此,在任一具體時刻,一個CPU的核心只會執行一條執行緒中的指令,為了能夠使得每個執行緒都線上程切換後能夠恢復在切換之前的程式執行位置,每個執行緒都需要有自己獨立的程式計數器,並且不能互相被幹擾,否則就會影響到程式的正常執行次序。所以程式計數器是執行緒私有,它的生命週期與執行緒相同,是一個非常小的記憶體空間,幾乎可以忽略不記。     此外如果執行緒正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是Natvie方法(Java呼叫非Java程式碼的介面。方法的實現由非Java語言實現,比如C或C++。),這個計數器值則為空(undefined)。程式計數器中儲存的資料所佔空間的大小不會隨程式的執行而發生改變,所以此區域不會出現OutOfMemoryError的情況。      4,java虛擬機器棧     與程式計數器一樣,Java虛擬機器棧(JavaVirtualMachineStacks)也是執行緒私有的,用通俗的話講它(或者說是虛擬機器棧中的區域性變量表部分)就是我們常常聽說到堆疊中的那個“棧記憶體”。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變量表(區域性變量表需要的記憶體在編譯期間就確定了所以在方法執行期間不會改變大小),運算元棧,動態連結,方法出口等資訊。每一個方法從呼叫至出棧的過程,就對應著棧幀在虛擬機器中從入棧到出棧的過程。     也可以這樣理解, Java棧中存放的是一個個的棧幀,每個棧幀對應一個被呼叫的方法,在棧幀中包括區域性變量表、運算元棧、指向當前方法所屬的類的執行時常量池(執行時常量池的概念在方法區部分會談到)的引用,方法返回地址(Return Address)和一些額外的附加資訊。當執行緒執行一個方法時,就會隨之建立一個對應的棧幀,並將建立的棧幀壓棧。當方法執行完畢之後,便會將棧幀出棧,即入棧出棧的過程。       5,本地方法棧     本地方法棧與Java棧的作用和原理非常相似。區別只不過是Java棧是為執行Java方法服務的,而本地方法棧則是為執行本地方法(Native Method)服務的。

6,java堆     被所有執行緒共享,在虛擬機器啟動時建立,用來存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。對於大多數應用來說,Java堆(Java Heap)是Java虛擬機器所管理的記憶體中最大的一塊。Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”。如果從記憶體回收的角度看,由於現在收集器基本都是採用的分代收集演算法,(下篇部落格會詳細介紹gc演算法和堆記憶體管理),所以Java堆中還可以細分為:新生代和老年代;新生代又有Eden空間、From Survivor空間、To Survivor空間三部分。Java 堆不需要連續記憶體,並且可以通過動態增加其記憶體,增加失敗會丟擲 OutOfMemoryError 異常。

7,方法區     與堆一樣,是被執行緒共享的區域。在方法區中,儲存了每個類的資訊(包括類的名稱、方法資訊、欄位資訊)、靜態變數、常量以及編譯器編譯後的程式碼等。在Class檔案中除了類的欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用來儲存編譯期間生成的字面量和符號引用。     和 Java 堆一樣不需要連續的記憶體,並且可以動態擴充套件,動態擴充套件失敗一樣會丟擲OutOfMemoryError 異常。對這塊區域進行垃圾回收的主要目標是對常量池的回收和對類的解除安裝,但是一般比較難實現,HotSpot 虛擬機器把它當成永久代(Permanent Generation)來進行垃圾回收。方法區邏輯上屬於堆的一部分,但是為了與堆進行區分,通常又叫“非堆”。     在 JDK1.7之前,HotSpot 使用永久代實現方法區;HotSpot 使用 GC 分代實現方法區帶來了很大便利;從 JDK1.7 開始HotSpot 開始移除永久代。其中符號引用(Symbols)被移動到 Native Heap中,字串常量和類引用被移動到 Java Heap中。在 JDK1.8 中,永久代已完全被元空間(Meatspace)所取代。元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制。

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

9,直接記憶體 直接記憶體(DirectMemory)並不是虛擬機器執行時資料區的一部分,但是這部分記憶體也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現。 在JDK1.4中新加入了NIO(NewInput/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆裡面的DirectByteBuffer物件作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高效能,因為避免了在Java堆和Native堆中來回複製資料。 顯然,本機直接記憶體的分配不會受到Java堆大小的限制,但是,既然是記憶體,則肯定還是會受到本機總記憶體(包括RAM及SWAP區或者分頁檔案)的大小及處理器定址空間的限制。伺服器管理員配置虛擬機器引數時,經常會忽略掉直接記憶體,使得各個記憶體區域的總和大於實體記憶體限制(包括物理上的和作業系統級的限制),從而導致動態擴充套件時出現OutOfMemoryError異常。