類由靜態到動態,會經歷運行時數據區這一步: 靜態編譯:把Java代碼編譯成位元組碼文件Class文件,它以靜態方式存在 類載入器:把Java位元組碼文件載入到記憶體中 【方法區】與【堆】是運行時數據區在所有線程間共用的,它們是存數據的地方 【虛擬機棧】,【本地方法棧】,【程式計數器】是運行時數據區線程私有 ...
類由靜態到動態,會經歷運行時數據區這一步:
靜態編譯:把Java代碼編譯成位元組碼文件Class文件,它以靜態方式存在
類載入器:把Java位元組碼文件載入到記憶體中
【方法區】與【堆】是運行時數據區在所有線程間共用的,它們是存數據的地方
【虛擬機棧】,【本地方法棧】,【程式計數器】是運行時數據區線程私有的,它們是執行邏輯的地方
以下代碼為例:
public class Person { String name; public void say(String name){ System.out.println(name); } public static void main(String[] args) { Person person = new Person(); person.say("Hello World!"); } }
方法區:
Person是存放在方法區的,方法區用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯器編譯後的代碼(JIT)
JIT:Java編譯成ByteCode(靜態編譯)然後交由CPU執行本地指令,在CPU執行本地指令之前,會將一些熱點代碼存儲下來保存在方法區,以提高效率,例如:
for (int i=0;i<10000;i++){ function(); }
堆:
當我們new Person的時候,這個對象實例會存在堆中,幾乎所有的對象實例都在這裡分配記憶體
當堆裡面沒有可分配記憶體的時候,會報OOM錯誤(記憶體超出)
程式計數器:類似彙編語言的寄存器(指令寄存器),用於存放下一條指令所在單元的地址的地方
虛擬機棧:存放局部變數表,操作數棧,動態連接/方法返回地址,分配基本類型和自定義對象的引用(局部變數表)
本地方法棧:為了Native的執行和調出(Java源碼中的很多Native方法)
在某些虛擬機中,本地方法棧+虛擬機棧+程式計數器=棧區
通常棧底是主方法Main,先入後出,第一個放入的方法是Main,最後執行的方法是Main
當死迴圈或遞歸調用棧中方法過多時候,會報StackOverFlow錯誤
堆棧之間如何配合:
觀察以下代碼:
String s = "1" + "2" + "3"; String s1 = "hello"; String s2 = new String("hello"); System.out.println(s1 == s2); System.out.println(s1.equals(s2));
列印的是:false\ntrue,說明s1和s2的值相同,地址不同
第一行創建了一個對象,編譯的時候進行了字元串摺疊(老版本JDK沒有優化,會創建四個對象)
第二行只創建一個對象,先在常量池創建字元串“hello”,然後將地址給s1
第三行創建了一個String對象,然後創建常量池字元串,所以棧中變數s2先指向堆中String對象地址,String對象地址再指向常量池中的字元串
因此s1和s2的地址不同
繼續看上面的這段代碼:
public class Person { String name; public void say(String name){ System.out.println(name); } public static void main(String[] args) { Person person = new Person(); person.say("Hello World!"); } }
(1)還沒new之前,person存入棧中的局部變數表(局部變數表可以存八種基本類型和引用類型)
(2)new Person之後會產生一個實例,存在堆中,局部變數表中的person引用指向堆中的實例
(3)在方法區中保存了Person類信息,比如屬性name和方法say
(4)調用say方法,局部變數表的person引用指向堆的Person實例,Person實例指向方法區say方法的地址,完成了一系列調用