當啓動一個Java程序時,一個虛擬機實例也就誕生了。當該程序關閉退出,這個虛擬機實例也就隨之消亡。若是在同一臺計算機上同時運行三個Java程序,將獲得三個Java虛擬機實例。每一個Java程序都運行於它本身的Java虛擬機實例中。java
Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。根據《Java虛擬機規範(第2版)》的規定,Java虛擬機所管理的內存將會包括如下幾個運行時數據區域數組
1.class文件:虛擬機並不關心Class的來源是什麼語言,只要它符合Java class文件格式就能夠在Java虛擬機中運行。使用Java編譯器能夠把Java代碼編譯爲存儲字節碼的Class文件,使用JRuby等其餘語言的編譯器同樣能夠把程序代碼編譯成Class文件。安全
2.類裝載器子系統:負責查找並裝載Class 文件到內存,最終造成能夠被虛擬機直接使用的Java類型。多線程
3.方法區:在類裝載器加載class文件到內存的過程當中,虛擬機會提取其中的類型信息,並將這些信息存儲到方法區。方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。因爲全部線程都共享方法區,所以它們對方法區數據的訪問必須被設計爲是線程安全的。this
4.堆:存儲Java程序建立的類實例。全部線程共享,所以設計程序時也要考慮到多線程訪問對象(堆數據)的同步問題。spa
5.Java棧:Java棧是線程私有的。每當啓動一個新線程時,Java虛擬機都會爲它分配一個Java棧。Java棧以幀爲單位保存線程的運行狀態。虛擬機只會直接對Java棧執行兩種操做:以幀爲單位的壓棧或出棧。當線程調用java方法時,虛擬機壓入一個新的棧幀到該線程的java棧中。當方法返回時,這個棧幀被從java棧中彈出並拋棄。一個棧幀包含一個java方法的調用狀態,它存儲有局部變量表、操做棧、動態連接、方法出口等信息。線程
6.程序計數器:一個運行中的Java程序,每當啓動一個新線程時,都會爲這個新線程建立一個本身的PC(程序計數器)寄存器。程序計數器的做用能夠看作是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。若是線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Natvie方法,這個計數器值則爲空(Undefined)。設計
7.本地方法棧:本地方法棧與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。任何本地方法接口都會使用某種本地方法棧。當線程調用Java方法時,虛擬機會建立一個新的棧幀並壓入Java棧。然而當它調用的是本地方法時,虛擬機會保持Java棧不變,再也不在線程的Java棧中壓入新的幀,虛擬機只是簡單地動態連接並直接調用指定的本地方法。若是某個虛擬機實現的本地方法接口是使用C鏈接模型的話,那麼它的本地方法棧就是C棧。指針
8.執行引擎:負責執行字節碼。方法的字節碼是由Java虛擬機的指令序列構成的。每一條指令包含一個單字節的操做碼,後面跟隨0個或多個操做數。執行引擎執行字節碼時,首先取得一個操做碼,若是操做碼有操做數,取得它的操做數。它執行操做碼和跟隨的操做數規定的動做,而後再取得下一個操做碼。這個執行字節碼的過程在線程完成前將一直持續。對象
Java中的棧
每當啓用一個線程時,JVM就爲他分配一個Java棧,棧是以幀爲單位保存當前線程的運行狀態。某個線程正在執行的方法稱爲當前方法,當前方法使用的棧幀稱爲當前幀,當前方法所屬的類稱爲當前類,當前類的常量池稱爲當前常量池。當線程執行一個方法時,它會跟蹤當前常量池。
每當線程調用一個Java方法時,JVM就會在該線程對應的棧中壓入一個幀,這個幀天然就成了當前幀。當執行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等等。
Java棧上的全部數據都是私有的。任何線程都不能訪問另外一個線程的棧數據。因此咱們不用考慮多線程狀況下棧數據訪問同步的狀況。
像方法區和堆同樣,Java棧和幀在內存中也沒必要是連續的,幀能夠分佈在連續的棧裏,也能夠分佈在堆裏
Java棧的組成元素——棧幀
棧幀由三部分組成:局部變量區、操做數棧、幀數據區。局部變量區和操做數棧的大小要視對應的方法而定,他們是按字長計算的。但調用一個方法時,它從類型信息中獲得此方法局部變量區和操做數棧大小,並據此分配棧內存,而後壓入Java棧。
局部變量區 局部變量區被組織爲以一個字長爲單位、從0開始計數的數組,類型爲short、byte和char的值在存入數組前要被轉換成int值,而long和 double在數組中佔據連續的兩項,在訪問局部變量中的long或double時,只需取出連續兩項的第一項的索引值便可,如某個long值在局部變量區中佔據的索引時三、4項,取值時,指令只需取索引爲3的long值便可。
下面就看個例子,好讓你們對局部變量區有更深入的認識。這個圖來自《深刻JVM》:
上面代碼片的方法參數和局部變量在局部變量區中的存儲結構以下圖:
上面這個圖沒什麼好說的,你們看看就會懂。可是,在這個圖裏,有一點須要注意:
runInstanceMethod的局部變量區第一項是個reference(引用),它指定的就是對象自己的引用,也就是咱們經常使用的this,可是在runClassMethod方法中,沒這個引用,那是由於runClassMethod是個靜態方法。
操做數棧和局部變量區同樣,操做數棧也被組織成一個以字長爲單位的數組。但和前者不一樣的是,它不是經過索引來訪問的,而是經過入棧和出棧來訪問的。可把操做數棧理解爲存儲計算時,臨時數據的存儲區域。下面咱們經過一段簡短的程序片斷外加一幅圖片來了解下操做數棧的做用。
int a = 100;
int b = 98;
int c = a+b;
從圖中能夠得出:操做數棧其實就是個臨時數據存儲區域,它是經過入棧和出棧來進行操做的。
幀數據區除了局部變量區和操做數棧外,Java棧幀還須要一些數據來支持常量池解析、正常方法返回以及異常派發機制。這些數據都保存在Java棧幀的幀數據區中。
當JVM執行到須要常量池數據的指令時,它都會經過幀數據區中指向常量池的指針來訪問它。
除了處理常量池解析外,幀裏的數據還要處理Java方法的正常結束和異常終止。若是是經過return正常結束,則當前棧幀從Java棧中彈出,恢復玻璃鋼液下泵發起調用的方法的棧。若是方法又返回值,JVM會把返回值壓入到發起調用方法的操做數棧。
爲了處理Java方法中的異常狀況,幀數據區還必須保存一個對此方法異常引用表的引用。當異常拋出時,JVM給catch塊中的代碼。若是沒發現,方法當即終止,而後JVM用幀區數據的信息恢復發起調用的方法的幀。而後再發起調用方法的上下文從新拋出一樣的異常。
棧的整個結構
在前面就描述過:棧是由棧幀組成,每當線程調用一個Java方法時,JVM就會在該線程對應的棧中壓入一個幀,而幀是由局部變量區、操做數棧和幀數據區組成。那在一個代碼塊中,棧究竟是什麼形式呢?下面是我從《深刻JVM》中摘抄的一個例子,你們能夠看看:
代碼片斷:
執行過程當中的三個快照:
上面所給的圖,只想說明兩件事情,咱們也可用此來理解Java中的棧:
一、只有在調用一個方法時,才爲當前棧分配一個幀,而後將該幀壓入棧。
二、幀中存儲了對應方法的局部數據,方法執行完,對應的幀則從棧中彈出,並把返回結果存儲在調用方法的幀的操做數棧中。