前文知道了java程序運行時在內存中的大概分佈,可是對於具體程序是如何運行的,看到一篇文章,直接轉載過來。java
(一)不含靜態變量的java程序運行時內存變化過程分析ide
代碼:oop
1 package oop; 2 3 /** 4 * 說明:實體類 5 * 6 * @author huayu 7 * @date 2018/8/3 8 */ 9 public class Birthday { 10 private int day; 11 private int month; 12 private int year; 13 //有參構造方法 14 public Birthday(int day, int month, int year) { 15 this.day = day; 16 this.month = month; 17 this.year = year; 18 } 19 20 public int getDay() { 21 return day; 22 } 23 24 public void setDay(int day) { 25 this.day = day; 26 } 27 28 public int getMonth() { 29 return month; 30 } 31 32 public void setMonth(int month) { 33 this.month = month; 34 } 35 36 public int getYear() { 37 return year; 38 } 39 40 public void setYear(int year) { 41 this.year = year; 42 } 43 44 @Override 45 public String toString() { 46 return "Birthday{" + 47 "day=" + day + 48 ", month=" + month + 49 ", year=" + year + 50 '}'; 51 } 52 } 53 54 55 56 57 58 59 60 61 import oop.Birthday; 62 63 /** 64 * 說明:測試類 65 * 66 * @author huayu 67 * @date 2018/8/3 68 */ 69 public class TestBirthday { 70 public static void main(String[] args) { 71 //new TestBirthday();至關於調用了TestBirthday類的無參構造方法(編譯器默認加的) 72 1. TestBirthday test=new TestBirthday(); 73 //調到這句在棧內存分配一塊名爲date,值爲9的內存 74 2. int date=9; 75 //new Birthday(7,7,1970);至關於調用了Birthday類中的有參構造方法 76 3. Birthday d1=new Birthday(7,7,1970); 77 4. Birthday d2=new Birthday(1,1,2000); 78 5. test.change1(date); 79 6. test.change2(d1); 80 7. test.change3(d2); 81 8. System.out.println("date="+date); 82 9. d1.toString(); 83 10. d2.toString(); 84 85 } 86 public void change1(int i){ 87 11. i=1234; 88 } 89 public void change2(Birthday b){ 90 12. b=new Birthday(22,2,2004); 91 } 92 public void change3(Birthday b){ 93 13. b.setDay(22); 94 } 95 }
內存過程分析:佈局
在作分析之前咱們應該預備的知識有:測試
1)棧內存儲的是局部變量,基礎類型的局部變量也分配在棧中,並且它只佔一塊內存:以下圖棧中的局部變量date,一個int類型變量分配了一塊int類型空間,四個字節,裏面裝了個值9,名字叫作date。this
2)引用類型在內存中佔兩大塊(棧中跟堆中),一塊在棧中存的是指向對象的引用(對象在堆中的地址),一塊在堆中存的是new出來的對象。(凡是new出東西來,則在堆內存中作分配):以下圖棧中的局部變量test,一個引用類型分配了一塊內存用於存test的引用(即對象在堆中的存儲地址,後期利用這個存儲地址去找到並操做堆中new出來這個引用的對象),它指向了在堆中new出來的對象的一塊內存(即圖中那塊未存東西的空位置)。spa
3)方法中的形參與局部變量等同.net
4)方法調用的時候是值傳遞code
5)方法執行完畢後,在棧中爲這個方法分配的局部變量的空間所有消失。對象
6)在堆中new出來的對象若棧中沒有了它的引用(也就是不用它了),後期會被GC清理掉那部分堆中的內存,而GC回收的具體時間不可控。
上面代碼中當執行到TestBirthday中main方法第四句時,內存裏面的佈局是這個樣子的(下圖1中所示堆中的d1對象顯示的好像是3塊,其實就是一個塊,也就是堆中就一個d1對象,我爲了看起來清楚才這麼畫的本身知道就能夠了,d2同理也是就一個對象)。
(1)執行change1(int i)方法
接下來咱們開始執行第五句chage1(int i)方法,而裏面有個形參i,當咱們調用這個方法的時候,首先應該在棧裏面爲形參(與局部變量等同)分配一塊空間,以下分配了一個空間名字叫i,值的話是實參傳了多少就是多少,在這實參是date且值爲9,因此形參i的值是9(方法調用的時候是值傳遞,至關於把date的值9複製給了i),此時的內存佈局爲圖2:
執行到第11句,i=1234;到這時i的值由9變爲了1234,可是date值不會變,由於i當時的值9是date複製了一份給它的,因此i的改變對date無影響。此時的內存佈局爲圖3:
當執行完change1方法後,形參i在棧中分配的空間被收回(注意這時date依然不受影響哈),此時的內存佈局爲圖4
(2)執行change2(Birthday b)方法
首先,形參要求傳一個Birthday b的引用類型,因此咱們將d1傳進來。Birthday b是一個局部變量,二話不說在棧內存空間內分配一個變量b,b中裝的是實參中傳的內容(即d1中的內容),當執行了這個方法以後,它會把d1中的值複製給b。此時b中的地址跟d1是同樣的,他們指向的是同一個對象(堆中的d1對象)。此時的內存佈局爲圖5:
當執行第12句b=new Birthday(22,2,2004);這一句時,堆中這是又會new出一個新的對象(凡是new出東西來,則在堆內存中作分配),此時棧中的引用地址指向新new出來的b的對象(注意到棧中b的引用地址也變化了)。此時的內存佈局爲圖6:
當方法change2執行完畢後,爲其在棧中分佈的局部變量空間也隨之消失。注意,此時棧中爲方法執行分佈的局部變量被收回了,可是它在堆上new出來的對象b不必定消失,它會等待GC來回收它,具體回收時間不可控,因此咱們也不肯定它何時能消失,可是弱棧中沒有了指向它的引用,這個b對象早晚會被GC清理掉,遲早會消失。此時的內存佈局爲圖7:
(3)執行change3(Birthday b)方法
首先,形參要求傳一個Birthday b的引用類型,因此咱們將d2傳進來。Birthday b是一個局部變量,二話不說在棧內存空間內分配一個變量b,b中裝的是實參中傳的內容(即d2中的內容),當執行了這個方法以後,它會把d2中的值複製給b。此時b中的地址跟d2是同樣的,他們指向的是同一個對象(堆中的d2對象)。此時的內存佈局爲圖8:
當執行到第13句b.setDay(22);(是Birthday類中的public void setDay(int day) {this.day = day;}這個方法),它會利用b這個引用去操做d2對象,當傳入實參22給int day後它會把值傳給this.day,則day的值真正被改變了(注意,此時的棧中b引用跟d2引用的值都沒變,仍是指向同一個對象)。此時的內存佈局爲圖9:
當方法change3執行完畢後,爲其在棧中分佈的局部變量空間也隨之消失。注意,雖然此時棧中b引用雖然消失了,但它指向的對象d2還有棧中的d2引用在,因此堆中對象d2不會消失。此時的內存佈局爲圖10:
change1,change2方法調用完後,或早或晚的棧內存,堆內存中分配的空間的都會被回收,沒起任何實質性做用,至關於白調了。而change3方法調用了實體類Birthday中的具體方法,確實並對堆內存中仍然被棧空間的引用d2所引用的堆內對象作了修改產生了實質性做用。
(二)含static靜態變量的java程序運行時內存變化過程分析
預備知識:static關鍵字
1)在類中,用static聲明的成員變量爲靜態成員變量,它爲該類的公用變量。在第一次使用時候被初始化,對於該類的全部對象來講,static成員變量只有一份。
2)用static聲明的方法爲靜態方法,在調用該方法時,不會將對象的引用傳遞給它,因此在static方法中不可訪問非static的成員。(靜態方法只能訪問靜態成員,由於非靜態方法的調用要先建立對象,在調用靜態方法時可能對象並無被初始化)。
3)能夠經過對象引用或類名(不須要實例化)訪問靜態成員。
4)static靜態變量存在在data seg數據區,就算是不new它,它也會在data seg部分保留一份。靜態變量是屬於整個類的,它不屬於某一個對象。
知識連接:字符串常量在內存中分配也是在data segment部分。
1 /** 2 * 說明:靜態變量內存分析舉例 3 * 4 * @author huayu 5 * @date 2018/8/3 6 */ 7 public class Cat { 8 //靜態成員變量,就算不new對象它也會在data seg裏面保存一份,它屬於整個類 9 //不屬於某個對象。int靜態變量能夠用來計數。 10 //對靜態值訪問:1.任何一個對象均可以訪問這個靜態對象,訪問的時候都是同一塊內存 11 //2.即使是沒有對象,也能夠經過 類名. 來訪問 如:System.out out是個靜態變量 12 1. private static int sid=0; 13 //非靜態成員變量 new對象的時候在堆內存對象中保存,每new一個對象產生一塊 14 2. private String name; 15 //非靜態成員變量 new對象的時候在堆內存對象中保存,每new一個對象產生一塊 16 3. private int id; 17 18 public Cat(String name) { 19 4. this.name = name; 20 5. id=sid++; 21 } 22 23 public void info(){ 24 6. System.out.println("My name is "+name+" No."+id); 25 } 26 27 public static void main(String[] args) { 28 //靜態變量sid屬於整個Cat類,不屬於某個對象,能夠用類名.來訪問,因此這兒沒有new任何對 29 //象,直接用類名.(Cat.sid)來訪問的。 30 7. Cat.sid=100; 31 //字符串常量分配在data seg 32 8. Cat mimi=new Cat("mimi"); 33 9. Cat pipi=new Cat("pipi"); 34 10. mimi.info(); 35 11. pipi.info(); 36 37 } 38 } 39 My name is mimi No.id=100 sid= 102 40 My name is pipi No.id=101 sid= 102
執行完7Cat.sid時,靜態變量sid值爲100。內存分佈狀態以下:
(1)執行第7句構造方法
第一步:執行第7句Cat mimi=new Cat("mimi");字符串常量「mimi」分佈在data segment部分,內存分佈以下(這兒看不懂的再從頭看這篇文章):
第二步:調到上面後就該到Cat的構造方法了,執行第4句this.name = name;這時根據傳入構造方法的name形參,棧中就會爲其開闢一塊名爲name的空間,實參「mimi」傳了進來,這時候棧中name的引用指向了data segment中的字符串常量「mimi」,由於this.name = name,因此自身成員變量的this.name也指向了data segment中的字符串常量「mimi」。
第三步:執行id=sid++;mimi對象的成員變量i值爲原來sid的值100,接下來sid++,將sid的值改成101,內存狀態以下圖:
第四步:構造方法執行完成後,爲執行這個方法在棧中分配的形參變量的內存空間收回,name在棧中的空間消失(固然,爲執行方法而在棧中分配的局部變量空間,方法執行完畢後都應該被收回了)
(2)執行Cat pipi=new Cat("pipi"); 方法跟執行上面那個構造方法原理是同樣的(固然,爲執行方法而在棧中分配的局部變量空間,方法執行完畢後都應該被收回了),你們本身畫一下,我這邊把最後的內存分佈狀態給一下你們:
原文詳見:https://blog.csdn.net/Myuhua/article/details/81385609