內存中的棧和堆
總結
個人理解,注意好棧區能存什麼類型值,堆區存什麼值,再者分清基本數據類型和引用數據類型就ok了。
內存中的堆和棧
> 棧區和堆區
一: 基本概念
1、java中的棧(stack)和堆(heap)是java在內存(ram)中存放數據的地方
2、堆區
存儲的全部是對象,每個對象都包含一個與之對應的class的信息。(class的目的是得到操作指令);
jvm只有一個heap區,被所有線程共享,不存放基本類型和對象引用,只存放對象本身。
堆的優劣勢:堆的優勢是可以動態的分配內存大小,生存期也不必事先告訴編譯器,java的垃圾收集器會自動收取這些不在使用的數
據,但缺點是,由於要在運行時動態分配內存,存取速度慢。
3、棧區
每一個線程包含一個stack區,只保存基本數據類型的對象和自定義對象的引用(不是對象),對象都存放在共享heap中;
每個棧中的數據(基本數據類型和對象引用)都是私有的,其他棧不能訪問;
棧分爲3部分:基本類型變量區、執行環境上下文、操作指令區(存放操作指令)
棧的優勢劣勢:存取速度比堆要快,僅次於直接位於CPU的寄存器,但必須確定的是存在stack中的數據大小與生存期必須是確定
的,缺乏靈活性。單個stack的數據可以共享。
stack:是一個先進後出的數據結構,通常保存方法中的參數,局部變量。
在java中,所有基本類型和引用類型都在stack中儲存,棧中數據的生存空間一般在當前scopes內
4、方法區
1、又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量;
2、方法區中包含的都是在程序中永遠的唯一的元素
二: Java中的數據類型有兩種。
一種是基本類型(primitive types), 共有8種,即int, short, long, byte, float, double, boolean, char(注意, 並沒有string的基本類型)。這種類型的定義是通過諸如int a = 3; long b = 255L;的形式來定義的,稱爲自動變量。值得注意的是,自動變量存的是字面值,不是類的實例,即不是類的引用,這裏並沒有類的存在。如int a = 3; 這裏的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,由於大小可知,生存期可知(這些字面值固定定義在某個程序塊裏面,程序塊退出後,字段值就消失了),出於追求速度的原因,就存在於棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
int a = 3;
int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變量爲a的引用,然後查找有沒有字面值爲3的地址,沒找到,就開闢一個存放3這個 字面值的地址,然後將a指向3的地址。接着處理int b = 3;在創建完b的引用變量後,由於在棧中已經有3這個字面值,便將b直接指向3的地址 。這樣,就出現了a與b同時均指向3的情況。
特別注意的是,這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變量修改了這個 對象的內部狀態,那麼另一個對象引用變量也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導致另一個指向此字面值的 引用的值也跟着改變的情況。如上例,我們定義完a與b的值後,再令a=4;那麼,b不會等於4,還是等於3。在編譯器內部,遇到a=4;時,它 就會重新搜索棧中是否有4的字面值,如果沒有,重新開闢地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
另一種是包裝類數據,如Integer, String, Double等將相應的基本數據類型包裝起來的類。這些類數據全部存在於堆中,Java用new() 語句來顯示地告訴編譯器,在運行時才根據需要動態創建,因此比較靈活,但缺點是要佔用更多的時間。
三: 實例探索
示例1
main()
int x=1;
show ()
int x=2
主函數main()中定義變量int x=1,show()函數中定義變量int x=1。最後show()函數執行完畢。
以上程序執行步驟:
第1步——main()函數是程序入口,JVM先執行,在棧內存中開闢一個空間,存放int類型變量x,同時附值1。
第2步——JVM執行show()函數,在棧內存中又開闢一個新的空間,存放int類型變量x,同時附值2。
此時main空間與show空間並存,同時運行,互不影響。
第3步——show()執行完畢,變量x立即釋放,空間消失。但是main()函數空間仍存在,main中的變量x仍然存在,不受影響。
示意圖如下:
![image](https://images2015.cnblogs.com/blog/1099884/201702/1099884-20170208230951901-930534799.jpg)
示例2
main()
int[] x=new int[3];
x[0]=20
主函數main()中定義數組x,元素類型int,元素個數3。
以上程序執行步驟
第1步——執行int[] x=new int[3];
隱藏以下幾分支
JVM執行main()函數,在棧內存中開闢一個空間,存放x變量(x變量是局部變量)。
同時,在堆內存中也開闢一個空間,存放new int[3]數組,堆內存會自動內存首地址值,如0x0045。
數組在棧內存中的地址值,會附給x,這樣x也有地址值。所以,x就指向(引用)了這個數組。此時,所有元素均未附值,但都有默認初始化值0。
第2步——執行x[0]=20
即在堆內存中將20附給[0]這個數組元素。這樣,數組的三個元素值分別爲20,0,0
示圖如下:
![image](https://images2015.cnblogs.com/blog/1099884/201702/1099884-20170208231402666-566810794.jpg)
示例3
main()
int[] x=new int[3];
x[0]=20
x=null;
以上步驟執行步驟
第1、2步——與示例2完全一樣,略。
第3步——執行x=null;
null表示空值,即x的引用數組內存地址0x0045被刪除了,則不再指向棧內存中的數組。此時,堆中的數組不再被x使用了,即被視爲垃圾,JVM會啓動垃圾回收機制,不定時自動刪除。
示圖如下:
![image](https://images2015.cnblogs.com/blog/1099884/201702/1099884-20170208231602291-1005997283.jpg)
示例4
main()
int[] x=new int[3];
int[] y=x;
y[1]=100
x=null;
以上步驟執行步驟
第1步——與示例2第1步一致,略。
第2步——執行int[] y=x,
在棧內存定義了新的數組變量內存y,同時將x的值0x0045附給了y。所以,y也指向了堆內存中的同一個數組。
第3步——執行y[1]=100
即在堆內存中將20附給[0]這個數組元素。這樣,數組的三個元素值分別爲0,100,0
第4步——執行x=null
則變量x不再指向棧內存中的數組了。但是,變量y仍然指向,所以數組不消失。
示圖如下
![image](https://images2015.cnblogs.com/blog/1099884/201702/1099884-20170208231954541-572479628.jpg)
示例5
Car c=new Car;
c.color="blue";
Car c1=new Car;
c1.num=5;
雖然是個對象都引用new Car,但是是兩個不同的對象。每一次new,都產生不同的實體
示圖如下
![image](https://images2015.cnblogs.com/blog/1099884/201702/1099884-20170221140004820-645574950.jpg)
示例6
Car c=new Car;
c.num=5;
Car c1=c;
c1.color="green";
c.run();
Car c1=c,這句話相當於將對象複製一份出來,兩個對象的內存地址值一樣。所以指向同一個實體,對c1的屬性修改,相當於c的屬性也改了。
示圖如下
![image](https://images2015.cnblogs.com/blog/1099884/201702/1099884-20170221141037179-1006525570.jpg)
四: 棧和堆的特點
棧:
函數中定義的基本類型變量,對象的引用變量都在函數的棧內存中分配。
棧內存特點,數數據一執行完畢,變量會立即釋放,節約內存空間。
棧內存中的數據,沒有默認初始化值,需要手動設置。
堆:
堆內存用來存放new創建的對象和數組。
堆內存中所有的實體都有內存地址值。
堆內存中的實體是用來封裝數據的,這些數據都有默認初始化值。
堆內存中的實體不再被指向時,JVM啓動垃圾回收機制,自動清除,這也是JAVA優於C++的表現之一(C++中需要程序員手動清除)。
五: 什麼是局部變量
定義在函數中的變量、定義在函數中的參數上的變量、定義在for循環內部的變量
JAVA在程序運行時,在內存中劃分5片空間進行數據的存儲。分別是:1:寄存器。2:本地方法區。3:方法區。4:棧。5:堆
1. 寄存器(register)。這是最快的存儲區,因爲它位於不同於其他存儲區的地方——處理器內部。但是寄存器的數量極其有限,所
以寄存器由編譯器根據需求進行分配。你不能直接控制,也不能在程序中感覺到寄存器存在的任何跡象。
2. 堆棧(stack)。位於通用RAM中,但通過它的“堆棧指針”可以從處理器哪裏獲得支持。堆棧指針若向下移動,則分配新的內存;
若向上移動,則釋放那些 內存。這是一種快速有效的分配存儲方法,僅次於寄存器。創建程序時候,JAVA編譯器必須知道存儲
在堆棧內所有數據的確切大小和生命週期,因爲它必須生成 相應的代碼,以便上下移動堆棧指針。這一約束限制了程序的靈活
性,所以雖然某些JAVA數據存儲在堆棧中——特別是對象引用,但是JAVA對象不存儲其 中。
3. 堆(heap)。一種通用性的內存池(也存在於RAM中),用於存放所以的JAVA對象。堆不同於堆棧的好處是:編譯器不需要知
道要從堆裏分配多少存儲區 域,也不必知道存儲的數據在堆裏存活多長時間。因此,在堆裏分配存儲有很大的靈活性。當你需要
創建一個對象的時候,只需要new寫一行簡單的代碼,當執行 這行代碼時,會自動在堆裏進行存儲分配。當然,爲這種靈活性必
須要付出相應的代碼。用堆進行存儲分配比用堆棧進行存儲存儲需要更多的時間。
4. 靜態存儲(static storage)。這裏的“靜態”是指“在固定的位置”。靜態存儲裏存放程序運行時一直存在的數據。你可用關鍵字
static來標識一個對象的特定元素是靜態的,但JAVA對象本身從來不會存放在靜態存儲空間裏。
5. 常量存儲(constant storage)。常量值通常直接存放在程序代碼內部,這樣做是安全的,因爲它們永遠不會被改變。有時,在
嵌入式系統中,常量本身會和其他部分分割離開,所以在這種情況下,可以選擇將其放在ROM中
6. 非RAM存儲。如果數據完全存活於程序之外,那麼它可以不受程序的任何控制,在程序沒有運行時也可以存在。
就速度來說,有如下關係:
寄存器 < 堆棧 < 堆 < 其他