JVM有哪些記憶體區域,分別用來做什麼的?我們就來把JVM開膛破肚
JVM載入回顧
上次我解釋了JVM是如何載入class的,以及有哪些載入器。請看下圖我們在進行一次簡單的回憶!
一個類從載入到使用,一般會經歷下面的這個過程 看圖:
載入->驗證->準備->初始化->使用->解除安裝銷燬
我們寫的位元組碼如何執行的
我們寫的程式碼被編譯成位元組碼,實際上還是java虛擬機器呼叫執行引擎來執行的。這裡我就不詳細說了,可以看我以前寫的文章。
JVM到底是怎麼劃分記憶體區域的
1,首先我們寫的java程式碼是不是要被載入到JVM進行執行,我給大家畫了一個整體的架構圖。 通過圖解我們知道java程式碼被編譯成了class,然後由我們的載入器載入到jvm,然後由java執行器執行編譯好的位元組碼,在程式執行的時期就會用到資料和相關的資訊,這個時候就需要java執行時區域,這個區域也是我們通常說的jvm記憶體,記憶體的管理也是對這個區域進行管理,比如分配記憶體和回收記憶體。 java執行時區域圖解:
這幾個區域我不想像別人那樣寫一大堆文字,然後你們看了一點都不知道是什麼東西。這個很難理解,接下來我將結合一些程式碼來講解。
我們思考一個問題,我們假設我們的程式碼要儲存哪些東西在執行時區域裡面呢?我大致歸納了一下,看是否和你們想的一樣哦。 首先我們寫的所有的class是不是都要有一個地方儲存啊?我們程式碼在執行的時候是不是要執行我們一個個的方法?在執行某個方法的時候,如果要建立物件是不是要有一個存放物件的地方啊?你有這些疑問的時候設計的人同樣是這樣的!
這個就是JVM為什麼要劃分不同的區域來存放了,它是為了我們寫好程式碼在執行的過程中根據需要來使用。
存放我們類的方法區
JDK1.8以前叫做方法區,我現在用的是1.8的JDK,JDK8把這塊區域叫做“元資料空間”。這個就專門存放我們寫的各種類的資訊。 結合我們程式碼來看看
package cn.changhong.conf.client.utils;
/**
* @author 獨秀天狼
*/
public class Luancher {
public static void main(String[] args) {
Message message=new Message();
SendMsg sender=new DingdingSendMsg();
sender.send(message);
}
}
根據我原來說的只要用到了class就會載入到JVM看圖理解比較好!
存放執行指令的程式計數器
/**
* @author 獨秀天狼
*/
public class Luancher {
public static void main(String[] args) {
Message message=new Message();
SendMsg sender=new DingdingSendMsg();
sender.send(message);
}
}
當前我們這個程式碼會被編譯成位元組碼,位元組碼就是計算器可以理解的語言,我們位元組碼的執行指令大概是如下,給大家看一下: 想看到程式碼的一些指令,可以用javap 來看我們的class轉換到指令是什麼樣的。
public class cn.changhong.conf.client.utils.Luancher {
public cn.changhong.conf.client.utils.Luancher();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class cn/changhong/conf/client/utils/Message
3: dup
4: invokespecial #3 // Method cn/changhong/conf/client/utils/Message."<init>":()V
7: astore_1
8: new #4 // class cn/changhong/conf/client/utils/DingdingSendMsg
11: dup
12: invokespecial #5 // Method cn/changhong/conf/client/utils/DingdingSendMsg."<init>":()V
15: astore_2
16: aload_2
17: aload_1
18: invokeinterface #6, 2 // InterfaceMethod cn/changhong/conf/client/utils/SendMsg.send:(Lcn/changhong/conf/client/utils/Message;)V
23: return
}
E:\apollo_conf\target\classes\cn\changhong\conf\client\utils>
比如“0: aload_0”就是位元組碼指令,計算機讀到這條指令就知道要做什麼了。 所以在這裡大家要明白:我們寫的程式碼最終會被轉換成位元組碼指令。 在位元組碼執行的時候就需要一個記憶體區域來記錄我們執行指令的位置。如圖 我們的程式基本上都是多執行緒執行指令的,每個執行緒執行指令都會有一個獨立的計數器,專門記錄當前的執行緒執行指令到那個位置了。如圖: 通過這樣的圖解是不是比較清晰了。
Java虛擬機器棧
當java程式碼在執行的時候,一定是執行緒執行某個方法,比如下面我寫出的這些程式碼,這個會有一個main執行緒去執行main方法的程式碼,當執行緒main執行main方法的時候,就會有對於的程式計數器來記錄當前main執行緒執行的指令位置。
/**
* @author 獨秀天狼
*/
public class Luancher {
public static void main(String[] args) {
Message message=new Message();
SendMsg sender=new DingdingSendMsg();
sender.send(message);
}
}
方法裡面我們會有一些區域性的成員變數,就當前這個程式碼就有message,sender因此我們JVM就必須要有一個區域來儲存當前的區域性變數等資料。這個就是java虛擬機器棧。 每個執行緒都有自己的虛擬機器棧,這個程式碼裡面就會有main執行緒的虛擬機器棧,用來存放自己執行的區域性變數。當執行方法就會建立一個棧幀 這個棧幀就儲存了局部變數,運算元棧,方法出口等,這個時候就會把main方法壓入到main執行緒建立的虛擬機器棧中,同時 message ,sender就會依次存放。如圖所示! 緊接著 sender.send()方法就來到了 DingdingSendMsg 裡面的這個send方法,這個方法裡面也有區域性變數
public class DingdingSendMsg implements SendMsg {
@Override
public void send(Message message) {
String title=message.getTitle();
System.out.println(title);
//TODO 傳送釘釘訊息
}
}
那這個時候也會為send方法建立一個棧幀,壓入到執行緒的虛擬機器棧裡面。如圖:
上述就是JVM中的java虛擬機器棧整個圖解過程,呼叫任何方法都會為這個方法建立一個棧幀然後入棧,這個棧幀裡面就儲存了局部變數的資料,和方法執行的相關的資訊,執行完了就出棧。 最後我們把整個過程圖解一下: 以上就java虛擬機器棧的整個圖解,希望大家已經明白了。
java物件的儲存 堆
我們現在已經知道了main執行緒執行main()方法,會有自己的程式計數器,還會把main方法的區域性變數依次壓入java虛擬機器棧中存放。
在上面的圖中還有一個非常關鍵的區域沒有說明,那就是java堆記憶體,這個就是存放我們程式碼建立的各種物件的。 比如剛剛那些程式碼裡面:
/**
* @author 獨秀天狼
*/
public class Luancher {
public static void main(String[] args) {
Message message=new Message();
SendMsg sender=new DingdingSendMsg();
sender.send(message);
}
}
這個程式碼裡面就有 new Message()和 new DingdingSendMsg()這2個物件建立,所以我們建立的這部分資料就會儲存在堆中。
然後區域性變數 message,sender引用了物件 Mesage,DingdingSendMsg,我用一個圖來說明。
總結
最後我將畫一個完整的圖給大家體會一下,這樣就更加的清晰和明瞭。
謝謝大家的閱讀,希望能給你們梳理清楚了這個難懂的JVM記憶體結構。面試再也不怕了,也真正的理解了,java工作的原理了。
喜歡就給我點贊。
本文中版權歸獨秀天狼團隊所有,轉載請標註