目錄html
Java語言是一門編譯型語言,須要將編寫的源代碼(.java文件)編譯以後(.class字節碼文件),經過 jvm 才能正常的執行,下面的內容記錄了一個程序從編寫到執行整個過程在內存中是怎麼一個變的。java
先了解下 JVM 的內存分佈,由於Java程序想要運行,就要依靠 JVM,能夠把JVM理解成Java程序和操做系統之間的橋樑,JVM 實現了Java 的平臺無關性,因而可知JVM的重要性。因此在學習 Java 內存分配原理的時候必定要牢記這一切都是在 JVM 中進行的,JVM 是內存分配原理的基礎與前提。jvm
從圖片中看,一共分爲了5大區域,分別是:方法區、堆、棧、本地方法區、程序計數器。函數
這裏咱們主要了解下 方法區、堆、 棧、這三個區域。學習
方法區是一塊全部線程共享的內存區域。
保存系統的類信息,好比,類的字段,方法,常量池等等。spa
方法區的大小決定了系統能夠保存多少個類,若是系統定義了太多的類,致使方法區溢出,虛擬機一樣會拋出內存溢出的錯誤操作系統
jdk1.6和jdk1.7方法區能夠理解爲永久區。.net
jdk1.8已經將方法區取消,替代的是元數據區。線程
jdk1.8的元數據區可使用參數-XX:MaxMetaspaceSzie設定大小,這是一塊堆外的直接內存,與永久區不一樣,若是不指定大小,默認狀況下,虛擬機會耗盡可用系統內存。指針
用來存放動態產生的數據,好比new出來的對象。注意建立出來的對象只包含屬於各自的成員變量,並不包括成員方法。由於同一個類的對象擁有各自的成員變量,存儲在各自的堆中,可是他們共享該類的方法,並非每建立一個對象就把成員方法複製一次。在堆中只會存儲成員方法的地址,在調用的時候,根據地址去方法區中執行對應的成員方法。
棧生命週期與線程相同。啓動一個線程,程序調用函數,棧幀被壓入棧中,函數調用結束,相應的是棧幀的出棧。
棧幀由局部變量表,操做數棧,幀數據區組成。
局部變量表:存放的是函數的入參,以及局部變量。
操做數棧:存放調用過程當中的計算結果的臨時存放區域。
幀數據區:存放的是異常處理表和函數的返回,訪問常量池的指針。
舉個例子,線程執行進入方法A,則會建立棧幀入棧,A方法調用了B方法,B棧幀入棧,B方法中調用C方法,C建立了棧幀壓入棧中,接下來是D入棧
反過來,D方法執行完,棧幀出棧,接着是C、B、A。
從上圖咱們看到了一個程序在內存中執行的過程。
上圖的執行流程:
1.從 disk 中將 MainApp.class 加載到 jvm 的方法區中。
2.執行 main 方法,將該 main 方法中包含的變量和函數,壓到棧中。
3.開始執行 main 方法中的指令,建立一個 animal 對象, 將 new 出來的 animal 對象存儲到堆中,animal 引用指向堆中的 animal 對象,堆中的 animal 對象指向方法區中的 Animal 類。
4.繼續執行 main 方法中的指令,調用 animal 對象中的 printName() 方法,這時 animal 應用調用 animal 對象, animal 對象找到方法區的 Animal 類中的 printName() 字節碼信息,根據該描述信息,開始執行 printName方法。
從左側咱們看到有兩個類,按照Java程序的執行流程,會把這兩個類編譯成 .class 文件,即圖中最右邊的 Phone.class he Demo01PhoneOne.class。
首先程序開始執行是從 main() 方法開始,這個時候會把 main() 方法壓到棧中,main() 方法中的第一句代碼是先建立一個 Phone 對象,當咱們 new 一個對象時,會把 new 出來的對象放到堆中,相對應的給這個對象分配一個地址值,在棧中會產生一個實例 one 會指向這個地址,能夠看到堆中的對象包含了自身的成員變量和成員方法的引用。
接着繼續執行下面的代碼,直接打印對象的屬性值,因爲對象屬性沒有進行賦值,因此輸出的都是對應數據類型的默認值。 繼續下面的操做,就是給對象的屬性進行賦值,因爲 one 是指向了對象,因此直接能夠進行操做,這時在堆中的屬性值就會被賦予對應的值了。再次打印的時候就會打印出對應的值。
再到後面,繼續調用了對象的成員方法,這個時候須要先在堆中找到這個成員方法的應用,而後找到方法區中將對應的代碼壓到棧中,繼續執行。調用方法會傳入對應的參數,也是放到棧中的,執行完這個方法以後,壓到棧中的這一部分代碼就會出棧,直到 main() 方法中全部的代碼執行完,棧中的內容也就所有消失,內存也就隨之釋放。
這裏和上面不一樣的是建立了兩個對象,可是操做的內容仍是和上面同樣的。惟一區別就是在調用成員方法時,調用的是同一個。
剛開始也說到了,同一個類建立多個對象時,他們是各自擁有本身的成員變量了,可是應用的成員方法倒是同一個。
從圖中咱們就能夠看出,給兩個對象進行賦值時,是會打印出不一樣的值的。調用方法時,使用的仍是同一個方法。
當我理解了前面兩個圖後,看到這裏應該也不會有什麼難度了,這裏咱們只 new 了一個對象,可是卻有兩個實例,從圖中也能夠看到堆裏面只有一個對象。
看到圖最左邊,咱們把 one 實例直接就賦值給了 two, 其實就是把 one 的地址值賦給了 two, 這時 two 也就和one 指向了同一個對象。這時去改變對象中的值,就會把 one 原來賦的值直接覆蓋掉。最終打印的就是 two 實例賦的值了。
使用對象類型做爲方法的參數,在傳遞的過程當中,實際上傳遞的是引用,即對象的地址值。當咱們在另一個方法中改變了這個對象的屬性時,對象原來的值就會被覆蓋。
對象類型做爲返回值也是同樣的道理,返回的實際是對象的地址值。
分清什麼是實例什麼是對象。Class a= new Class(); 此時 a 叫實例,而不能說 a 是對象。實例在棧中,對象在堆中,操做實例其實是經過實例的指針間接操做對象。多個實例能夠指向同一個對象。
棧中的數據和堆中的數據銷燬並非同步的。方法一旦結束,棧中的局部變量當即銷燬,可是堆中對象不必定銷燬。由於可能有其餘變量也指向了這個對象,直到棧中沒有變量指向堆中的對象時,它才銷燬,並且還不是立刻銷燬,要等垃圾回收掃描時才能夠被銷燬。
以上的棧、堆、代碼段、數據段等等都是相對於應用程序而言的。每個應用程序都對應惟一的一個JVM實例,每個JVM實例都有本身的內存區域,互不影響。而且這些內存區域是全部線程共享的。這裏提到的棧和堆都是總體上的概念,這些堆棧還能夠細分。
類的成員變量在不一樣對象中各不相同,都有本身的存儲空間(成員變量在堆中的對象中)。而類的方法倒是該類的全部對象共享的,只有一套,對象使用方法的時候方法才被壓入棧,方法不使用則不佔用內存。
對象類型做爲方法的參數或者方法的返回值時,傳遞的都是對象的地址值。再其餘地方修改這個對象的屬性值時,原有的值就會被覆蓋掉。
參考文章: