轉載:java程序調用內存的變化過程

 前文知道了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

相關文章
相關標籤/搜索