人們常說堆棧堆棧,堆和棧是內存中兩處不一樣的地方,什么樣的數據存在棧,又是什么樣的數據存在堆中?
這里淺談Java中的棧和堆
首先,將結論寫在前面,后面再用例子加以驗證。
Java的棧中存儲以下類型數據,棧對應的英文單詞是Stack
基本類型
引用類型變量
方法
棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。
棧中主要存放一些基本類型的變量(int, short, long, byte, float, double, boolean, char)和對象句柄。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。
Java的堆中存儲以下類型數據,堆對應的英文單詞是Heap
實例對象
在函數中定義的一些基本類型的變量(8種)和對象的引用變量都是在函數的棧Stack內存中分配。當在一段代碼塊中定義一個變量時,java就在棧中為這個變量分配內存空間,當超過變量的作用域后,java會自動釋放掉為該變量分配的內存空間,該內存空間可以立刻被另作他用。
堆Heap內存用於存放由new創建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以后就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當於為數組或者對象起的一個別名,或者代號。
引用變量是普通變量,定義時在棧中分配內存,引用變量在程序運行到作用域外釋放。而數組&對象本身在堆中分配,即使程序運行到使用new產生數組和對象的語句所在地代碼塊之外,數組和對象本身占用的堆內存也不會被釋放,數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然占着內存,在隨后的一個不確定的時間被垃圾回收器釋放掉。這個也是java比較占內存的主要原因,實際上,棧中的變量指向堆內存中的變量,這就是 Java 中的指針!
class Person { int age; } public class LearnHeap { public static void main(String args[]){ int a=10; Person person = new Person(); person.age =20; change(a,person); System.out.println("a="+ a+",and person.age = "+person.age); } static void change(int a1, Person person){ a1 = 11; person.age= 21; System.out.println("a1="+ a1+",and age1 = "+person); } }
兩次的輸出結果是什么?猜測下。
以上程序內存加載的執行步驟:
第1步 —— main()函數是程序入口,JVM先執行,首先將main方法壓入棧中,在棧內存中開辟一個空間,存放int類型變量a,同時附值10。
在堆中分配一片區域,用來存放和創建Person對象,這片內存區域會有屬於自己的內存地址,假設是1001,然后給成員變量賦值,age=20
執行結束后,構造防范弾棧,Person創建完成,將Person的內存地址1001賦值給person(此處person小寫,是引用變量類型)
第2步 —— JVM執行change()函數,在棧內存中又開辟一個新的空間,存放int類型變量a和對象Person中person
此時main空間與change空間並存,同時運行,互不影響。
第3步 —— change()方法執行,將a賦值為11,person對象的堆中年齡age賦值為21
第4步 —— change()執行完畢,變量a立即釋放,空間消失。但是main()函數空間仍存在,main中的變量a仍然存在,不受影響。而person在堆中對應的地址,所指的age已經賦值=21
實際輸出如下:
結論:
如果a()方法中的基本類型(8個)變量x傳入b()方法中,並在b()中進行了修改,則a()方法中的x的值保持不變
如果a()方法中的引用類型 變量x傳入b()方法中,並在b()中進行了修改,則a()方法中的x的值與b()保持一致
下面對8中基本類型進行簡單的測試
package heapandStack; public class LearnHeap02 { public static void main(String args[]){ byte b=10; short s=20; int i=30; long l =40l; float f =12.34f; double d = 100.123d; char c = 'A'; boolean flag = true; change(b,s,i,l,f,d,c,flag); System.out.println(b); System.out.println(s); System.out.println(i); System.out.println(l); System.out.println(f); System.out.println(d); System.out.println(c); System.out.println(flag); } static void change(byte a, short b, int c,long d, float f, double g, char h,boolean x){ a =11; b=21; c =31; d =41l; f=12.99f; g= 200.123d; h ='V'; x =false; System.out.println(a); System.out.println(b); System.out.println(c); System.out.println(d); System.out.println(f); System.out.println(g); System.out.println(h); System.out.println(x); } }
下面測試一下數組,是不是屬於實例對象類型
public class LearnHeap03 { public static void main(String args[]){ int a[] ={1,2,3}; change(a); for(int i:a) System.out.print(i+" "); } static void change(int[] a){ a[0]=4; a[1]=5; for(int i:a) System.out.print(i+" "); System.out.println("============ "); } }
從結果看出,數組的值被改變了
Java種8種基本數據類型,並不包含String,String的值會被change函數改變嗎?String應該存在棧中,還是堆中呢?
先直接上測試代碼
public class Learn04 { public static void main(String args[]){ String s1 = new String("abcd"); String s2 = "asdfghjkl"; System.out.println(s1+", "+s2); change(s1,s2); System.out.println(s1+", "+s2); } static void change(String s1,String s2){ s1 ="123456"; s2 ="000000"; System.out.println(s1+", "+s2); } }
兩種的形式來創建String,第一種是用new()來新建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。 而第二種是先在棧中創建一個對String類的對象引用變量s2,然后查找棧中有沒有存放"asdfghjkl",如果沒有,則將"asdfghjkl"存放進棧,並令str指 向”abc”,如果已經有”asdfghjkl” 則直接令s2指向“asdfghjkl”。
既然講到兩種形式創建String,下面講一個String兩種形式創建的區別,先看一段代碼
public class Learn05 { public static void main(String args[]){ String s1 = new String("abcd"); String s2 = "abcd"; boolean a =s1.equals(s2); boolean b =(s1==s2); System.out.println(a); System.out.println(b); String s3 = "abcd"; boolean a1 =s3.equals(s2); boolean b1 =(s3==s2); System.out.println(a1); System.out.println(b1); } }