一、基礎篇(JVM記憶體結構)
一.概念
JAVA的JVM的記憶體可分為3個區:堆(heap)、棧(stack)和方法區(method)
1)堆區:
a.儲存的全部是物件,每個物件都包含一個與之對應的class的資訊。(class的目的是得到操作指令)
b.jvm只有一個堆區(heap)被所有執行緒共享,堆中不存放基本型別和物件引用,只存放物件本身
2)棧區:
a.每個執行緒包含一個棧區,棧中只儲存基礎資料型別的物件和自定義物件的引用(不是物件),物件都存放在堆區中
b.每個棧中的資料(原始型別和物件引用)都是私有的,其他棧不能訪問。
c.棧分為3個部分:基本型別變數區、執行環境上下文、操作指令區(存放操作指令)。
3)方法區:
a.又叫靜態區,跟堆一樣,被所有的執行緒共享。方法區包含所有的class和static變數。
b.方法區中包含的都是在整個程式中永遠唯一的元素,如class,static變數。
二.案例解析
Test.java
//執行時, jvm 把Test的資訊都放入方法區 public class Test { /** * @Description: main方法 * @param @param args * @return void * @throws * @date 2018-11-27 下午2:01:23 */ public static void main(String[] args) { //main 方法本身放入方法區。 MyObject test1=new MyObject("測試1"); test1.printName(); } }
MyObject.java
//執行時, jvm 把MyObject的資訊都放入方法區
public class MyObject {
/*範例名稱 */
private String name;//new MyObject例項後, name 引用放入棧區裡, name 物件放入堆裡
/*構造方法 */
public MyObject(String name){
this .name = name;
}
/*輸出方法*/
public void printName(){//printName方法本身放入 方法區裡。
System.out.println(name);
}
}
跟著Test程式碼走一遍(圖示):
1)載入Test類:
首先執行指令:“java Test”,系統收到發出的指令,啟動一個Java虛擬機器程序,這個程序首先從classpath中找到Test.class檔案,讀取這個檔案中的二進位制資料,然後把Test類的類資訊存放到執行時資料區的方法區中。
2)執行main()方法:
Java虛擬機器定位到方法區中Test類的main()方法的位元組碼,開始執行它的指令。這個main()方法的第一條語句就是:MyObject test1=new MyObject("測試1")。就是讓java虛擬機器建立一個MyObject 例項,並且使引用變數test1引用這個例項。
具體過程:
a.建立一個MyObject 例項,去方法區先找到MyObject 類的型別資訊。首次沒找到,方法區裡還沒有MyObject 類。所以Java虛擬機器立即載入了MyObject 類,把MyObject 類的型別資訊存放在方法區裡。
b.Java虛擬機器首先在堆區中為一個新的MyObject 例項分配記憶體, 這個MyObject 例項持有著指向方法區的MyObject 類的型別資訊的引用。這裡所說的引用,實際上指的是MyObject 類的型別資訊在方法區中的記憶體地址,而這個地址就存放了在MyObject 例項的資料區裡。
c.在JAVA虛擬機器程序中,每個執行緒都會擁有一個方法呼叫棧,用來跟蹤執行緒執行中一系列的方法呼叫過程,棧中的每一個元素就被稱為棧幀,每當執行緒呼叫一個方法的時候就會向方法棧壓入一個新幀。這裡的幀用來儲存方法的引數、區域性變數和運算過程中的臨時資料。
d.位於“=”前的test1是一個在main()方法中定義的變數,它是一個區域性變數,它被會新增到了執行main()方法的主執行緒的JAVA方法呼叫棧中。而“=”將把這個test1變數指向堆區中的MyObject 例項,它持有指向MyObject 例項的引用。
e.JAVA虛擬機器將繼續執行後續指令,執行test1的printName()方法,當JAVA虛擬機器執行test1.printName()方法時,JAVA虛擬機器根據區域性變數test1持有的引用,定位到堆區中的MyObject 例項,再根據MyObject 例項持有的引用,定位到方法去中MyObject 類的型別資訊,從而獲得printName()方法的位元組碼,接著執行printName()方法包含的指令。