程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠看作是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令。java
字節碼指令、分支、循環、跳轉、異常處理、線程恢復等基礎功能都要依賴這個計數器來完成。數組
每條線程都有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲。如上圖所示,咱們稱這類內存區域爲 : 線程私有內存。數據結構
若是線程正在執行的是一個 Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是 Native 方法,這個計數器值則爲空(Undefined)。post
此內存區域是惟一一個在 Java 虛擬機中沒有規範任何 OutOfMemoryError 狀況的區域。.net
Java 虛擬機棧也是線程私有的,它的生命週期與線程相同(隨線程而生,隨線程而滅)線程
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常;設計
若是虛擬機棧能夠動態擴展,若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryError異常;(當前大部分 JVM 均可以動態擴展,只不過 JVM 規範也容許固定長度的虛擬機棧)code
Java 虛擬機棧描述的是 Java 方法執行的內存模型:每一個方法執行的同時會建立一個棧幀。 對於咱們來講,主要關注的 stack 棧內存,就是虛擬機棧中局部變量表部分。cdn
定義
局部變量表(Local Variable Table)是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。對象
編譯器肯定容量
在Java程序編譯爲class文件時,就在方法的Code屬性的 max_locals 數據項中肯定了 該方法所須要分配的局部變量表的最大容量。
最小單位爲變量槽(Slot)
一個Slot 能夠存放一個32位之內的數據類型,包括基本數據類型 (boolean、byte、char、short、int、float、long、double)「String 是引用類型」,對象引用 (reference 類型) 和 returnAddress 類型(它指向了一條字節碼指令的地址)。
與JVM棧區別
本 地方法棧(Native Method Stack)與虛擬機棧所發揮的做用是很是類似的,它們之間的區別不過是虛擬機棧爲虛擬機執行 Java 方法(也就是字節碼)服務,而本地方法棧爲虛擬機使用到的 Native 方法服務。
自由實現
Java 虛擬機規範對本地方法棧使用的語言、使用方法與數據結構並無強制規定,所以能夠由虛擬機自由實現。例如:HotSpot 虛擬機直接將本地方法棧和虛擬機棧合二爲一。
異常
同虛擬機棧相同,Java 虛擬機規範對這個區域也規定了兩種異常狀況StackOverflowError 和 OutOfMemoryError異常。
對於大多數應用來講,Java 堆 (Java Heap) 是 JVM所管理的內存中最大的一塊。
Java 堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。
此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。
數組引用變量是存放在棧內存中,數組元素是存放在堆內存中。
Java 堆是垃圾收集器管理的主要區域,所以不少時候也被稱做爲 "GC 堆"。
從內存回收的角度看,Java 堆中還能夠細分爲: 新生代 和 老年代。
程序新建立的對象都是重新生代分配內存,新生代由 Eden Space 和兩塊相同大小的 Survivor Space(一般又稱 S0 和 S1 或 From 和 To) 構成。
詳見JVM常見參數設置
從內存分配角度,線程共享的 Java 堆可能劃分出多個線程私有的分配緩衝區(TLAB)。
Java 堆能夠處於物理不連續的內存空間中,只要邏輯是連續的便可,就像咱們的磁盤空間同樣。
在實現時,便可以實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的 (經過 -Xmx 和 -Xms 控制)。
若是堆上沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出 OutOfMemoryError
異常。
方法區 (Method Area) 與 Java 堆同樣, 是各個線程共享的內存區域。
它用於存儲已經被虛擬機加載的類信息 、常量 、靜態變量、即時編譯器編譯後的代碼等數據
運行時常量池 (Runtime Constant Pool) 是方法區的一部分。
雖然 JVM規範把方法區描述爲堆的一個邏輯部分, 可是它卻又一個別名叫作 Non-Heap(非堆), 目的應該是與 Java 堆區分開來.
方法區 和 永久代(Permanent Generation), 本質上二者並不相等。
僅僅是由於 HotSpot 虛擬機的設計團隊選擇把 GC 分代收集擴展至方法區, 或者說使用永久代來實現方法區而已。
這樣 HotSpot 的垃圾收集器能夠像管理 Java 堆同樣管理這部份內容, 可以省去專門爲方法區編寫內存管理代碼的工做。
所以, 對於 HotSpot 虛擬機, 根據官方發佈的路線圖信息, 如今也有放棄永久代並逐步採用 Native Memory 來實現方法區的規劃了, 在目前已經發布的 JDK1.7 的 HotSpot 中, 已經把本來放在永久代的字符串常量池移出。
JVM規範對方法區的限制很是寬鬆
和堆同樣, 容許固定大小, 也容許可擴展的大小, 還能夠選擇不實現垃圾回收。 相對而言, 垃圾收集行爲在這個區域是比較少出現的, 可是並不是數據進入了方法區就如同進入永久代的名字同樣」 永久」 存在了。
這區域的內存回收目標主要是針對常量池的回收和對類型的卸載, 通常來講, 這個區域的回收」 成績」 比較難以使人滿意, 尤爲是對類型的卸載, 條件至關苛刻, 可是這部分區域的回收確實是存在必要的。
在 Sun 公司的 BUG 列表裏, 曾出現過的若干個嚴重的 BUG 就是因爲低版本的 HotSpot 虛擬機對此區域未徹底回收而致使內存泄漏。
當方法區沒法知足內存分配需求時, 將拋出 OutOfMemoryError
異常。
內存泄露: 指程序中動態分配內存給一些臨時對象,可是對象不會被 GC 所回收,它始終佔用內存。即被分配的對象可達但已無用,可用內存愈來愈少。
內存溢出: 指程序運行過程當中沒法申請到足夠的內存而致使的一種錯誤。內存溢出一般發生於老年代或永久代垃圾回收後,仍然無內存空間容納新的 Java 對象的狀況。
內存泄露是內存溢出的一種誘因,不是惟一因素。
Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池 (Constant Pool Table),用於存放編譯期生成的字面量和符號引用,這部份內容(也能夠稱爲 .Class 文件中的靜態常量池)將在類加載後進入方法區的運行時常量池中存放。
字面量
比較接近 Java 語言層面的常量概念,如文本字符串、聲明爲 final 的常量值等。(final 修飾的成員變量和類變量【類變量:靜態成員變量】)
符號引用
符號引用就是字符串,這個字符串包含足夠的信息,以供實際使用時能夠找到相應的位置。
你好比說某個方法的符號引用,如:「java/io/PrintStream.println:(Ljava/lang/String;)V」。裏面有類的信息,方法名,方法參數等信息。
當第一次運行時,要根據字符串的內容,到該類的方法表中搜索這個方法。
運行一次以後,符號引用會被替換爲直接引用,下次就不用搜索了。
直接引用就是偏移量,經過偏移量虛擬機能夠直接在該類的內存區域中找到方法字節碼的起始位置。
除了保存 Class 文件中描述的符號引用外,還會把編譯出來的直接引用也存儲在運行時常量池中。
Java 語言並不要求常量必定只有編譯期才能產生,也就是並不是置入 Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的即是 String 類的 intern() 方法。
運行時常量池受方法區內存的限制,當常量池沒法再申請到內存時也會拋出OutofMemoryError異常。
參考來源:
周志明 《深刻理解Java虛擬機》
Java 內存區域——堆,棧,方法區等
知乎網友Intopass分享