1. 程式人生 > >(轉)java程序調用內存變化過程分析(詳細)

(轉)java程序調用內存變化過程分析(詳細)

舉例 static成員 bsp 根據 執行方法 pip 狀態 font ear

原博地址:

https://blog.csdn.net/Myuhua/article/details/81385609

(一)不含靜態變量的java程序運行時內存變化過程分析

代碼:

 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 } 96

內存過程分析:

在做分析以前我們應該預備的知識有:

1)棧內存儲的是局部變量,基礎類型的局部變量也分配在棧中,而且它只占一塊內存:如下圖棧中的局部變量date,一個int類型變量分配了一塊int類型空間,四個字節,裏面裝了個值9,名字叫做date。

2)引用類型在內存中占兩大塊(棧中跟堆中),一塊在棧中存的是指向對象的引用(對象在堆中的地址),一塊在堆中存的是new出來的對象。(凡是new出東西來,則在堆內存中做分配):如下圖棧中的局部變量test,一個引用類型分配了一塊內存用於存test的引用(即對象在堆中的存儲地址,後期利用這個存儲地址去找到並操作堆中new出來這個引用的對象),它指向了在堆中new出來的對象的一塊內存(即圖中那塊未存東西的空位置)。

3)方法中的形參與局部變量等同

4)方法調用的時候是值傳遞

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"); 方法跟執行上面那個構造方法原理是一樣的(當然,為執行方法而在棧中分配的局部變量空間,方法執行完畢後都應該被收回了),大家自己畫一下,我這邊把最後的內存分布狀態給一下大家:

技術分享圖片

從以上sid(static id)的變化不難看出,int的靜態變量可以用作計數用。

(3)將以上Cat程序的static靜態變量static int sid;再改為非靜態變量 int sid;後做內存分析對比

代碼:

 1 /**
 2  * 說明:將上面代碼靜態變量改為非靜態的再做內存分析
 3  *
 4  * @author huayu
 5  * @date 2018/8/3 下午6:32
 6  */
 7 public class Cat1 {
 8     private  int sid=0;
 9     private  String name;
10     private int id;
11  
12     public Cat1(String name) {
13         this.name = name;
14         id=sid++;
15     }
16  
17     public void info(){
18         System.out.println("My name is "+name+" No.id="+id+" sid= "+sid);
19     }
20  
21     public static void main(String[] args) {
22 //        Cat1.sid=100;
23         Cat1 mimi=new Cat1("mimi");
24         Cat1 pipi=new Cat1("pipi");
25         mimi.info();
26         pipi.info();
27  
28     }
29 }
30 輸出結果:
31 My name is mimi No.id=0 sid= 1
32 My name is pipi No.id=0 sid= 1

對以上程序進行內存分析:這兒上面以前詳細的講過,大家親自動手去畫一畫,感受了解一下靜態變量跟非靜態變量的區別,下面我給貼個最終狀態的圖(記得,為執行方法而在棧中分配的局部變量空間,最終方法執行完畢後都應該被收回了):

技術分享圖片

從以上過程不難看出當靜態變量static int sid;改為非靜態變量int sid;後,每new一個對象,sid不再發生變化,故用它來計數是不可能了。

(轉)java程序調用內存變化過程分析(詳細)