1、程序計數器java
程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。 程序員
通俗地講,線程執行的任務在計算機語言中,被當作是一條條的指令。線程須要一個計數器來幫助它標記執行了什麼指令,以及選取下一條指令。字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。多線程
每條線程被CPU執行以後(由於java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間來實現的--時間片輪轉),須要切換下一條,爲了使線程能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各線程之間的計數器互不影響,獨立存儲。編輯器
這一塊內存區域爲「線程私有」的內存。ide
2、Java 虛擬機棧(具體應該叫作方法調用棧)函數
Java 虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,生命週期與線程相同,由於它描述的是Java方法執行的內存模型。優化
Java棧是一個線程的執行區域, 它保存着一個線程中的方法的調用狀態, 也能夠說, 一個Java線程的運行狀態, 都由一個Java棧來保存。 在這個棧中, 每一方法對應一個棧幀, 請注意區分棧幀和棧這兩個概念。 棧指的是整個線程的執行棧, 棧幀是棧中的一個單位, 每一個方法對應一個棧幀(棧幀隨着一個方法的調用開始而建立,這個方法調用完成而銷燬)。 JVM會對Java棧執行兩種操做: 壓棧和出棧。 這兩種操做在執行時都是以幀(棧幀)爲單位的。 當調用了一個新的方法, 就會壓入一個棧幀, 當一個方法調用完成, 就會彈出這個方法的棧幀, 回到調用者的棧幀。 spa
舉例來講, 若是方法a調用了方法b, 而方法b中調用了方法c。 這個過程當中的方法調用和返回的裝狀態是這樣的(其中圖中兩條虛線之間表示Java棧,每一個方塊表示一個特定方法的棧幀)操作系統
一個線程中方法的調用鏈可能會很長,不少方法都同時處於執行狀態。對於JVM執行引擎來講,在在活動線程中,只有位於JVM虛擬機棧棧頂的元素纔是有效的,即稱爲當前棧幀,與這個棧幀相關連的方法稱爲當前方法,定義這個方法的類叫作當前類。 線程
由上圖能夠看出,Java棧中存放的是一個個的棧幀,每一個棧幀對應一個被調用的方法,在棧幀中包括局部變量表(Local Variables)、操做數棧(Operand Stack)、指向當前方法所屬的類的運行時常量池(運行時常量池的概念在方法區部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。當線程執行一個方法時,就會隨之建立一個對應的棧幀,並將創建的棧幀壓棧。當方法執行完畢以後,便會將棧幀出棧。所以可知,線程當前執行的方法所對應的棧幀一定位於Java棧的頂部。對於全部的程序設計語言來講,棧這部分空間對程序員來講是不透明的。
每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
在Java虛擬機的規範中,對這個區域規定了兩種異常狀況:
①局部變量表(Local Variable Table)
局部變量表(Local Variable Table)是一組變量值存儲空間,用於存放方法參數和方法內定義的局部變量。局部變量表的容量以變量槽(Variable Slot)爲最小單位,Java虛擬機規範並無定義一個槽所應該佔用內存空間的大小,可是規定了一個槽應該能夠存放一個32位之內的數據類型。
在Java程序編譯爲Class文件時,就在方法的Code屬性中的max_locals數據項中肯定了該方法所需分配的局部變量表的最大容量。(最大Slot數量)
一個局部變量能夠保存一個類型爲boolean、byte、char、short、int、float、reference和returnAddress類型的數據。reference類型表示對一個對象實例的引用。returnAddress類型是爲jsr、jsr_w和ret指令服務的,目前已經不多使用了。
虛擬機經過索引定位的方法查找相應的局部變量,索引的範圍是從0~局部變量表最大容量。若是Slot是32位的,則遇到一個64位數據類型的變量(如long或double型),則會連續使用兩個連續的Slot來存儲。
②操做數
操做數就是操做數據
Java 程序編譯以後就變成了一條條字節碼指令,其形式相似彙編,但和彙編有不一樣之處:彙編指令的操做數存放在數據段和寄存器中,可經過存儲器或寄存器尋址找到須要的操做數;而 Java 字節碼指令的操做數存放在操做數棧中,當執行某條帶 n 個操做數的指令時,就從棧頂取 n 個操做數,而後把指令的計算結果(若是有的話)入棧。所以,當咱們說 JVM 執行引擎是基於棧的時候,其中的「棧」指的就是操做數棧。舉個簡單的例子對比下彙編指令和 Java 字節碼指令的執行過程,好比計算 1 + 2,在彙編指令是這樣的:
1 2 |
|
而 JVM 的字節碼指令是這樣的:
1 2 3 |
|
因爲操做數棧是內存空間,因此字節碼指令沒必要擔憂不一樣機器上寄存器以及機器指令的差異,從而作到了平臺無關。
注意,局部變量表中的變量不可直接使用,如需使用必須經過相關指令將其加載至操做數棧中做爲操做數使用。好比有一個方法 void foo(),其中的代碼爲:int a = 1 + 2; int b = a + 3;,編譯爲字節碼指令就是這樣的:
1 2 3 4 5 6 7 8 9 |
|
須要說明的是,局部變量表以及操做數棧的容量的最大值在編譯時就已經肯定了,運行時不會改變。而且局部變量表的空間是能夠複用的,例如,當指令的位置超出了局部變量表中某個變量 a 的做用域時,若是有新的局部變量 b 要被定義,b 就會覆蓋 a 在局部變量表的空間。
③動態鏈接
在一個class文件中,一個方法要調用其餘方法,須要將這些方法的符號引用轉化爲其在內存地址中的直接引用,而符號引用存在於方法區中的運行時常量池。
Java虛擬機棧中,每一個棧幀都包含一個指向運行時常量池中該棧所屬方法的符號引用,持有這個引用的目的是爲了支持方法調用過程當中的動態鏈接(Dynamic Linking)。
這些符號引用一部分會在類加載階段或者第一次使用時就直接轉化爲直接引用,這類轉化稱爲靜態解析。另外一部分將在每次運行期間轉化爲直接引用,這類轉化稱爲動態鏈接。
④方法返回
當一個方法開始執行時,可能有兩種方式退出該方法:
正常完成出口是指方法正常完成並退出,沒有拋出任何異常(包括Java虛擬機異常以及執行時經過throw語句顯示拋出的異常)。若是當前方法正常完成,則根據當前方法返回的字節碼指令,這時有可能會有返回值傳遞給方法調用者(調用它的方法),或者無返回值。具體是否有返回值以及返回值的數據類型將根據該方法返回的字節碼指令肯定。
異常完成出口是指方法執行過程當中遇到異常,而且這個異常在方法體內部沒有獲得處理,致使方法退出。
不管是Java虛擬機拋出的異常仍是代碼中使用athrow指令產生的異常,只要在本方法的異常表中沒有搜索到相應的異常處理器,就會致使方法退出。
不管方法採用何種方式退出,在方法退出後都須要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能須要在當前棧幀中保存一些信息,用來幫他恢復它的上層方法執行狀態。
方法退出過程實際上就等同於把當前棧幀出棧,所以退出能夠執行的操做有:恢復上層方法的局部變量表和操做數棧,把返回值(若是有的話)壓如調用者的操做數棧中,調整PC計數器的值以指向方法調用指令後的下一條指令。
通常來講,方法正常退出時,調用者的PC計數值能夠做爲返回地址,棧幀中可能保存此計數值。而方法異常退出時,返回地址是經過異常處理器表肯定的,棧幀中通常不會保存此部分信息。
總結
1. 每一個線程包含一個棧區,棧中局部變量表保存基礎數據類型的對象和自定義對象的引用(不是對象)。對象都存放在堆區中。
2. 每一個棧中的數據(基礎數據類型和對象引用)都是私有的,其餘棧不能訪問。
3. 棧分爲3個部分:基本類型變量,執行環境上下文,操做指令區(存放操做指令).
4. 在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。
5. 當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當該變量退出該做用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做他用。
3、本地方法棧
與虛擬機同樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemeoryError 異常。
與虛擬機同樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemeoryError 異常。
擴展:Native Method:"A native method is a Java method whose implementation is provided by non-java code."
爲何有Native Method:有些層次的任務用java實現起來不容易,或者咱們對程序的效率很在乎時
①與java環境外交互
有時java應用須要與java外面的環境交互。這是本地方法存在的主要緣由,你能夠想一想java須要與一些底層系統如操做系統或某些硬件交換信息時的狀況。本地方法正是這樣一種交流機制:它爲咱們提供了一個很是簡潔的接口,並且咱們無需去了解java應用以外的繁瑣的細節。
②與操做系統交互
JVM支持着java語言自己和運行時庫,它是java程序賴以生存的平臺,它由一個解釋器(解釋字節碼)和一些鏈接到本地代碼的庫組成。然而無論怎 樣,它畢竟不是一個完整的系統,它常常依賴於一些底層(underneath在下面的)系統的支持。這些底層系統經常是強大的操做系統。經過使用本地方法,咱們得以用java實現了jre的與底層系統的交互,甚至JVM的一些部分就是用C寫的,還有,若是咱們要使用一些java語言自己沒有提供封裝的操做系統的特性時,咱們也須要使用本地方法。
③Sun's Java
Sun的解釋器是用C實現的,這使得它能像一些普通的C同樣與外部交互。jre大部分是用java實現的,它也經過一些本地方法與外界交互。例如:類java.lang.Thread 的 setPriority()方法是用java實現的,可是它實現調用的是該類裏的本地方法setPriority0()。這個本地方法是用C實現的,並被植入JVM內部,在Windows 95的平臺上,這個本地方法最終將調用Win32 SetPriority() API。這是一個本地方法的具體實現由JVM直接提供,更多的狀況是本地方法由外部的動態連接庫(external dynamic link library)提供,而後被JVM調用。
4、Java 堆
對大部分應用來講,java堆是java虛擬機所管理的內存中最大的一塊。java堆是被全部線程共享的一塊內存區域(即一個JVM只有一個堆),在虛擬機啓動時建立。此內存區域的惟一目的是存放對象實例,幾乎全部的對象實例都在這裏分配內存。
Java 堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續便可。在實現時,既能夠實現成固定大小的,也能夠是可擴展的(經過-Xmx 和 -Xms 控制)。若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出 OutOfMemoryError 異常。
此外,Java 堆是垃圾收集器器管理的主要區域。大體能夠分紅:新生代和老生代,還可再細分。可是進行細分的目的是爲了更好地回收內存或更快的分配內存。
4、方法區
方法區(Method Area)與 Java 堆 同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編輯器編譯後的代碼等數據。
方法區是堆的一個邏輯部分,可是它與java堆又是不一樣的,因此它有了一個別名——非堆(Non-Heap)。
方法區中的內存通常不會被 GC 回收,GC 也難回收,因此被取名爲「永久代」,意思是永久存在。這區域的內存回收目標主要是針對常量池的回收和對類的卸載。可是「永久代」中的數據並不是真的永久存在,只是回收比較麻煩。
根據 Java 虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出 OutOfMemoryError 異常。