在 JAVA 中,有六個不同的地方可以存儲數據:
1. 寄存器( register )。這是最快的存儲區,因為它位於不同於其他存儲區的地方——處理器內部。但是寄存器的數量極其有限,所以寄存器由編譯器根據需求進行分配。你不能直接控制,也不能在程序中感覺到寄存器存在的任何跡象。
2. 棧( stack )。位於通用 RAM 中,但通過它的“棧指針”可以從處理器獲得支持。堆棧指針若向下移動,則分配新的內存;若向上移動,則釋放那些內存。這是一種快速有效的分配存儲方法,存取僅次於寄存器。創建程序時候, JAVA 編譯器必須知道存儲在棧內所有數據的確切大小和生命周期,因為它必須生成相應的內存空間,以便上下移動堆棧指針。這一約束限制了程序的靈活性,所以雖然某些 JAVA 數據存儲在棧中——特別是對象引用,但是 JAVA 對象不存儲其中。
3. 堆( heap )。一種通用性的內存池(也存在於 RAM 中),用於存放所有的 JAVA 對象。堆不同於棧的好處是:編譯器不需要知道要從堆里分配多少存儲區域,也不必知道存儲的數據在堆里存活多長時間。因此,在堆里分配存儲有很大的靈活性。當你需要創建一個對象的時候,只需要 new 寫一行簡單的代碼,當執行這行代碼時,會自動在堆里進行存儲分配。當然,為這種靈活性必須要付出相應的代價:用堆進行存儲分配比用堆棧進行存儲存儲需要更多的時間。
4. 靜態存儲( static storage )。這里的“靜態”是指“在固定的位置”(盡管也位於RAM)。靜態存儲里存放程序運行時一直存在的數據。你可用關鍵字 static 來標識一個對象的特定元素是靜態的,但 JAVA 對象本身從來不會存放在靜態存儲空間里。
5. 常量存儲( constant storage )。常量值通常直接存放在程序代碼內部,這樣做是安全的,因為它們永遠不會被改變。有時,在嵌入式系統中,常量本身會和其他部分分割開,所以在這種情況下,可以選擇將其放在 ROM 中。(這點的一個例子便是字符串常量池,所有字面字符串和字符串常量表達式都被自動intered從而被放到一個特殊的靜態存儲空間里。)
6. 非 RAM 存儲。如果數據完全存活於程序之外,那么它可以不受程序的任何控制,在程序沒有運行時也可以存在。
上面這段話摘取自《 Thinking in Java 》』
---------------------------------------------------------------------
堆 是一個運行時數據區 , 類的對象從中分配空間。這些對象通過 new 建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的, Java 的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配內存,存取速度較慢。 java 中的對象和數組都存放在堆中。
棧 的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量( int, short, long, byte, float, double, boolean, char )和對象引用。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。 假設我們同時定義:
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=4 ;那么編譯器會重新搜索棧中是否有 4 值,如果沒有,則將 4 存放進來,並令 a 指向 4 ;如果已經有了,則直接將 a 指向這個地址。因此 a 值的改變不會影響到 b 的值。要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況 a 的修改並不會影響到 b, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量。
以上內容也是摘抄自網上。
疑惑:《Thinking in java 4》中不是這樣講的,書中說對於基元數據的存儲是在stack中用變量直接存值。對於這點,暫可采取上邊的理解,有待日后明確。
---------------------------------------------------------------------
下面我自己來舉幾個例子:
public class TestStr { public static void main(String[] args) { // 以下兩條語句創建了1個對象。"鳳山"存儲在字符串常量池中 String str1 = "鳳山" ; String str2 = "鳳山" ; System.out.println(str1==str2);//true // 以下兩條語句創建了2個對象,存儲在堆內存中 String str3 = new String("天峨" ); String str4 = new String("天峨" ); System.out.println(str3==str4);//false // 以下兩條語句創建了1個對象。9是存儲在棧內存中 int i = 9; int j = 9; System.out.println(i==j);//true // 以下兩條語句創建了1個對象。1對象存儲在棧內存中 Integer l = 1;//裝箱 Integer k = 1;// 裝箱 System.out.println(l==k);//true //由於沒有了裝箱,以下兩條語句創建了2個對象。兩個1對象存儲在堆內存中 Integer l1 = new Integer(1); Integer k1 = new Integer(1); System.out.println(l1==k1);//false 內容來自17jquery // 以下兩條語句創建了1個對象。i1,i2變量存儲在棧內存中,兩個256對象存儲在堆內存中 Integer i1 = 256; Integer i2 = 256; System.out.println(i1==i2);//false } }
對於以上最后兩個關於 Integer 對象的例子,在自動裝箱時對於值從 –128 到 127 之間的值,使用一個實例。
下面是對字符串常量池()的一個例子:
String s1 = "aaa" + "bbb"; // 產生了 1 個對象。
由於常量的值在編譯的時候就被確定了。在這里, "aaa" 和 "bbb" 都是常量,因此變量 s1 的值在編譯時就可以確定。這行代碼編譯后的效果等同於:
String s1 ="aaabbb";
因此這里只創建了一個對象 "aaabbb" ,並且它被保存在字符串池里了。
另:“ == “ 在判斷對象時,其實是根據對象在堆棧中的地址判斷對象是不是一樣,而不是根據 hashcode 值。
在網上看見這段對 Java String 中的 HashCode 和 equal 的總結比較好,記錄如下:
1. hashSet 中比較是否重復的依據是 a.hasCode () =b.hasCode () && a.equals ( b ) 內容來自17jquery
2. String 的 hashCode 依據: 以依賴於 char[i] 的 int 值以和 char[i] 的排列序的算法計算出的(可以去看看源碼)。不依賴 String 的 ref.
3. String 的 equals 依據: a==b || ( a.length=b.length && { a[i]=b[i] } )
4. 只有用 a==b 時比校的才是比校的 ref ,也就是說這時才是比校是 a 與 b 是不是同一個對象
5. 結論: 兩個不同 ref 的 String 可能會被認為是集合中的同一個元素。
---------------------------------------------------------------------
下面主要介紹 JAVA 中的堆、 棧 和 常量池 :
1. 寄存器
最快的存儲區, 由編譯器根據需求進行分配,我們在程序中無法控制。
2. 棧
存放基本類型的變量數據和對象的引用,但對象本身不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象存放在常量池中。)
3. 堆
存放所有new出來的對象。
4. 靜態域
存放靜態成員(static定義的)
5. 常量池
存放字符串常量和基本類型常量(public static final)。
6. 非RAM存儲
硬盤等永久存儲空間
這里我們主要關心棧,堆和常量池,對於棧和常量池中的對象可以共享,對於堆中的對象不可以共享。棧中的數據大小和生命周期是可以確定的,當沒有引用指向數據時,這個數據就會消失。堆中的對象的由垃圾回收器負責回收,因此大小和生命周期不需要確定,具有很大的靈活性。
對於字符串:其對象的引用都是存儲在棧中的,如果是編譯期已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才能確定的就存儲在堆中。對於equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。
如以下代碼:
String s1 = "china" ; String s2 = "china" ; String s3 = "china" ; String ss1 = new String ( "china" ); String ss2 = new String ( "china" ); String ss3 = new String ( "china" );
對於基礎類型的變量和常量:變量和引用存儲在棧中,常量存儲在常量池中。
如以下代碼:
int i1 = 9 ; int i2 = 9 ; int i3 = 9 ; public static final int INT1 = 9 ; public static final int INT2 = 9 ; public static final int INT3 = 9 ;
對於成員變量和局部變量:成員變量就是方法外部,類的內部定義的變量;局部變量就是方法或語句塊內部定義的變量。局部變量必須初始化。形式參數是局部變量,局部變量的數據存在於棧內存中。棧內存中的局部變量隨着方法的消失而消失。
成員變量存儲在堆中的對象里面,由垃圾回收器負責回收。
如以下代碼:
class BirthDate { private int day; private int month; private int year; public BirthDate ( int d, int m, int y ) { day = d; month = m; year = y; }
省略 get,set 方法 …… }
public class Test{ public static void main ( String args[] ) { int date = 9 ; Test test = new Test (); test.change ( date ); BirthDate d1= new BirthDate ( 7 , 7 , 1970 ); } public void change1 ( int i ) { i = 1234 ; } }
對於以上這段代碼,date為局部變量,i,d,m,y都是形參為局部變量,day,month,year為成員變量。下面分析一下代碼執行時候的變化:
1. main 方法開始執行:
int date = 9 ;
date 局部變量,基礎類型,引用和值都存在棧中。
2. test 為對象引用,存在棧中,對象(new Test())存在堆中。
Test test = new Test ();
3.
test.change ( date );
i 為局部變量,引用和值存在棧中。當方法change執行完成后,i就會從棧中消失。
4.
BirthDate d1= new BirthDate ( 7 , 7 , 1970 );
d1 為對象引用,存在棧中,對象(new BirthDate())存在堆中,其中d,m,y為局部變量存儲在棧中,且它們的類型為基礎類型,因此它們的數據也存儲在棧中。day,month,year為成員變量,它們存儲在堆中(new BirthDate()里面)。當BirthDate構造方法執行完之后,d,m,y將從棧中消失。
5.main 方法執行完之后,date變量,test,d1引用將從棧中消失,new Test(),new BirthDate()將等待垃圾回收。
希望通過以上內容的介紹,能夠給你帶來幫助