前面提到了類加載到內存中,jvm會把內存劃分紅不一樣的數據區域,那加載的類是分配到哪裏呢?下圖是內存的各個區域,包括:方法區、堆、虛擬機棧、本地方法棧、程序計數器。java
方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
類的加載中提到了類加載的五個階段。在加載階段,會將字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構,在準備階段,會將變量所使用的內存都將在方法區中進行分配。數據結構
來一個簡單的代碼,計算(1+2)*3並返回多線程
public int cal() { int a = 1; int b = 2; int c = 3; return (a + b) * c; }
這段代碼在加載到虛擬機的時候,就變成了如下的字節碼,虛擬機執行的時候,就會一行行執行。
java是多線程的,在線程切換回來後,它須要知道原先的執行位置在哪裏。用來記錄這個執行位置的,就是程序計數器,爲了保證線程間的計數器相互不影響,這個內存區域是線程私有的。jvm
虛擬機棧也是線程私有的,生命週期與線程相同。每一個線程都有本身的虛擬機棧,若是這個線程執行了一個方法,就會建立一個棧幀,方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
好比下面的例子,fun1調用fun2,fun2調用fun3,fun3建立Hello對象。this
public void fun1() { fun2(); } public void fun2() { fun3(); } public void fun3() { Hello hello = new Hello(); }
調用的時候,流程圖以下:
執行完成的時候,流程圖以下:
每個棧幀都包括了局部變量表、操做數棧、動態鏈接、方法返回地址和一些額外的附加信息。局部變量主要是存放方法參數以及方法內部定義的局部變量,操做數棧是一個後入先出棧,當方法剛剛開始執行的時候,這個方法的操做數棧是空的,在方法的執行過程當中,會有各類字節碼指令往操做數棧中寫入和提取內容,也就是出棧/入棧操做。
咱們經過上面(1+2)*3的例子,把方法區、程序計數器、虛擬機棧的協同工做理一下。首先經過javap查看它的字節碼,通過類加載器加載後,此時這個字節碼存在方法區中。stack表示棧深度是2,locals是本地變量的slot個數,args_size是入參的個數,默認是this。棧的深度、本地變量個數,入參個數,都是在編譯器決定的。
以下圖,指令的位置是方法區,局部變量和操做數棧的位置是虛擬機棧,程序計數器就在程序計數器(這個下面的圖就不在重複)。
當執行偏地址爲0的指令的時候,程序計數器爲0,局部變量第一個值是this,當前的指令就是方法區0:iconst_1
,指令iconst_1就是把int常量值1進棧,這個1就到了虛擬機棧的操做數棧中,
當執行偏地址爲1的指令的時候,程序計數器爲1,把操做數棧的值賦值到局部變量,此時操做數棧清空了,局部變量多了一個1,這條指令執行完,就是對應上面int a=1的語句。
另外b,c兩個語句的賦值,對應着2,3,4,5指令,這邊再也不重複。執行完5後,以下圖所示:
執行6的時候,是執行iload_1,就是把第二個int型局部變量壓入棧頂,這裏的變量是1。
執行7的時候,是執行iload_2,就是把第三個int型局部變量壓入棧頂,這裏的變量是2。
執行8的時候,是iadd語句,指的是棧頂的兩個int型元素出棧,獲得結果後再壓入棧頂。
執行9的時候,把棧頂的元素3,賦值到第五個局部變量。
執行到11的時候,把第五個局部變量值壓入棧頂,執行到13的時候,把第四個局部變量值壓入棧頂,執行14的時候,棧頂的兩個int型元素出棧,相乘後的結果入棧,執行15的時候,從當前方法返回當前棧頂int型元素。這些與上面的相加差很少,就不在累述了。spa
堆內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。好比上面的fun1調用fun2,fun2調用fun3,fun3建立Hello對象。fun3方法中建立對象時,就是在堆中建立的,而且把地址賦值給fun3的局部變量。Java堆中還能夠細分爲:新生代和老年代;新生代還細分爲Eden空間、From Survivor空間、To Survivor空間。線程
總體流程以下,先把java文件編譯成class文件,經過類加載器加載到方法區。線程調用方法的時候,會建立一個棧幀,讀取方法區的字節碼執行指令,執行指令的時候,會把執行的位置記錄在程序計數器中,若是建立對象,會在堆內存中建立,方法執行完,這個棧幀就會出棧。3d