JVM以內存區域總結

概述


  對於Java程序員來講,在虛擬機自動內存管理機制的幫助下,再也不須要像C/C++程序員那樣要爲每個new操做去寫配對的delete/free代碼,不容易出現內存泄漏和內存溢出的問題,由虛擬機管理內存,這一切看起來很美好。不過,也正是由於Java程序員把內存控制的權利交給了Java虛擬機,一旦出現內存泄漏和溢出方面的問題,若是不瞭解虛擬機是怎樣使用內存的,那麼排查錯誤將是一項異常艱難的工做。html

運行時數據區域


1. Java程序執行過程概述

在討論JVM內存區域以前,先來看一下Java程序的執行過程:java

  如上圖所示,首先Java源文件(.java後綴文件)會被Java編譯器變異爲字節碼文件(.class後綴文件),而後由JVM中的類加載器加載各個類的字節碼文件,加載完畢以後,交由 JVM執行引擎執行。在整個程序執行的過程當中,JVM會用一段空間來存儲程序執行期間須要用到的數據和相關信息,這段空間通常被稱做爲Runtime Data Area(運行時數據區域),也就是咱們常說的JVM內存。

2. 運行時數據區分區概述

  根據《Java虛擬機規範》的規定,運行時數據區一般包括這幾個部分:程序計數器(Program Counter Register)、Java虛擬機棧(JVM Statck)、本地方法棧(Native Method Stack)、Java堆(Java Heap)、方法區(Method Area)。詳細見下圖:程序員

3. 運行數據區各分區到底存儲了什麼?

  • 程序計數器(Program Counter Register)

  程序計數器,也有稱做爲PC寄存器。瞭解計算機組成原理的同窗對這個概念應該不陌生,在計算機組成原理中程序計數器是指CPU中的寄存器,它保存的是當前執行的指令地址(也能夠說是保存下一條指令的所在存儲單元的地址),當CPU須要執行指令時,須要從程序計數器中獲得當前須要執行的指令所在存儲單元的地址,而後根據獲得的地址獲取到指令,在獲得指令以後,程序計數器便自動加1或根據轉移指針獲得下一天指令的地址,如此循環,直至執行完全部的指令。數組

  雖然JVM中的程序計數器並不像計算機中的程序計數器同樣是物理概念上的PC寄存器,可是JVM中的程序計數器的功能跟計算機中的程序計數器的功能邏輯是等同的, 也就是說是用來指示執行哪條指令的。數據結構

  因爲在JVM中,多線程是經過線程間的輪流切換並分配處理器執行時間的方式來實現的, 在任何一個肯定的時刻,一個處理器(對多核處理器來講是一個內核)都會執行一條線程的指令。所以,爲了線程切換後還能恢復到正確的執行位置,每條線程都須要一個獨立的程序計數器,各個線程之間的計數器互不影響,獨立存儲,咱們稱這類內存區域爲 「線程私有」 的內存。多線程

注: 在JVM規範中規定,若是線程執行的是非native方法,則程序計數器中保存的是當前須要執行的指令的地址;若是線程執行的是native方法,則程序計數器中的值是undefined。函數

  因爲程序計數器中存儲的數據所佔空間的大小不會隨程序的執行而發生改變,所以,對於程序計數器是不會發生內存溢出現象(OutOfMemory)的。spa

  • Java虛擬機棧(JVM Statck)

  虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的過程當中同時會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息,並將創建的棧幀壓棧,當方法執行完畢以後,便會將棧幀出棧。(由此可知,Java棧中存放的是一個個棧幀,線程當前執行的方法所對應的棧幀一定位於Java棧的頂部。)簡單來講,每個方法從調用直至執行的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。線程

局部變量表
複製代碼

用來存儲方法中的局部變量(包括在方法中聲明的非靜態變量以及函數形參)。對於基本數據類型的變量,則直接存儲它的值,對於引用類型的變量,則存的是指向對象的引用。局部變量表的大小在編譯器就能夠肯定其大小了,所以在程序執行期間局部變量表的大小是不會改變的。設計

操做數棧
複製代碼

想必學過數據結構中的棧的朋友想必對錶達式求值問題不會陌生,棧最典型的一個應用就是用來對錶達式求值。想一想一個線程執行方法的過程當中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。所以能夠這麼說,程序中的全部計算過程都是在藉助於操做數棧來完成的。

指向運行時常量池的引用
複製代碼

由於在方法執行的過程當中有可能須要用到類中的常量,因此必需要有一個引用指向運行時常量。

方法返回地址
複製代碼

當一個方法執行完畢以後,要返回以前調用它的地方,所以在棧幀中必須保存一個方法返回地址。

注:因爲每一個線程正在執行的方法可能不一樣,所以每一個線程都會有一個本身的Java棧,互不干擾。即,爲線程私有內存區,生命週期:伴隨着線程的產生於死亡。

  • 本地方法棧(Native Method Stack)

  本地方法棧與Java棧的做用和原理很是類似。區別只不過是Java棧是爲執行Java方法服務的,而本地方法棧則是爲執行本地方法(Native Method)服務的。在JVM規範中,並無對本地方發展的具體實現方法以及數據結構做強制規定,虛擬機能夠自由實現它。在HotSopt虛擬機中直接就把本地方法棧和Java棧合二爲一。

  • Java堆(Java Heap)

  在C語言中,堆這部分空間是惟一一個程序員能夠管理的內存區域。程序員能夠經過malloc函數和free函數在堆上申請和釋放空間。那麼在Java中是怎麼樣的呢?

  Java中的堆是用來存儲對象自己以及數組(固然,數組引用是存放在Java棧中的)。只不過和C語言中的不一樣,在Java中,程序員基本不用去關心空間釋放的問題,Java的垃圾回收機制會自動進行處理。所以這部分空間也是Java垃圾收集器管理的主要區域。另外,堆是被全部線程共享的,在JVM中只有一個堆。

  • 方法區(Method Area)

  方法區在JVM中也是一個很是重要的區域,它與堆同樣,是被線程共享的區域。在方法區中,存儲了每一個類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及編譯器編譯後的代碼等

  在Class文件中除了類的字段、方法、接口等描述信息外,還有一項信息是常量池,用來存儲編譯期間生成的字面量和符號引用。

  在方法區中有一個很是重要的部分就是運行時常量池(Runtime Constant Pool),它是每個類或接口的常量池的運行時表示形式,在類和接口被加載到JVM後,對應的運行時常量池就被建立出來。固然並不是Class文件常量池中的內容才能進入運行時常量池,在運行期間也可將新的常量放入運行時常量池中,好比String的intern方法。

  在JVM規範中,沒有強制要求方法區必須實現垃圾回收。不少人習慣將方法區稱爲「永久代」,是由於HotSpot虛擬機以永久代來實現方法區,從而JVM的垃圾收集器能夠像管理堆區同樣管理這部分區域,從而不須要專門爲這部分設計垃圾回收機制。不過自從JDK7以後,Hotspot虛擬機便將運行時常量池從永久代移除了。

參考資料


相關文章
相關標籤/搜索