注:僅是整理總結,發現錯誤之處望及時告知,感激不盡。今日看到關於常量池、堆的東西,發現心裏一點概念也不記得了,故做次筆記加深印象。
一、Java中數據存放的位置
(缺少硬件部分的關聯)
1、寄存器
——最快的存儲區,處理器內部,由編譯器分配
2、棧
——僅次於寄存器的存儲方法,位於通用RAM中,由堆棧指針的移動決定新增或釋放內存。存放基本類型的變量數據和對象,數組的引用,但對象本身不存放在棧中,而是在堆或常量池中
3、堆
——一種通用性的內存池(RAM中),存放所有Java對象(new出來的),動態分配(即運行時)
4、靜態存儲(static storage)
——存放程序運行時一直存在的數據,static標識的元素
5、常量池
——常量通常直接存放在程序代碼內部,字符串常量和基本類型常量(public static final)
6、非RAM存儲:硬盤等永久存儲空間
二、棧、堆、常量池的區別
速度:寄存器>棧>堆>其他
對於棧和常量池中的對象可以共享,堆中的對象不可共享(共享是什麼層面上的意思??)
棧中的數據大小和生命週期是可以確定的,當沒有引用指向數據時,這個數據就會消失;堆中的對象由垃圾回收器扶着回收
對於字符串:其對象的引用都是存儲在棧中的,如果是編譯器已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才能確定的近存儲在堆中。 對於通過 new 產生一個字符串(假設爲 ”china” )時,會先去常量池中查找是否已經有了
”china” 對象,如果沒有則在常量池中創建一個此字符串對象,然後堆中再創建一個常量池中此 ”china” 對象的拷貝對象。這也就是有道面試題: String s = new String(“xyz”); 產生幾個對象?一個或兩個,如果常量池中原來沒有 ”xyz”, 就是兩個。
對於基礎類型的變量和常量:變量和引用存儲在棧中,常量存儲在常量池中。
對於成員變量和局部變量:成員變量就是方法外部,類的內部定義的變量;局部變量就是方法或語句塊內部定義的變量。局部變量必須初始化。形式參數是局部變量,局部變量的數據存在於棧內存中。棧內存中的局部變量隨着方法的消失而消失。成員變量存儲在堆中的對象裏面,由垃圾回收器負責回收。
如以下Java代碼
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 test = new 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()將等待垃圾回收。
三、具體使用
1、對於基本類型,定義方式諸如int a = 3;char b = 'a';的形式來定義的稱爲自動變量,自動變來個存的是字面值,不是類的實例(即不是類的引用),a是指向int類型的引用,指向3這個字面值。而3這個字面值就存在於棧中。
關於棧中數據共享的解釋:相等的字面值的引用指向棧內同一個字面值。例int x=1;int y=1;首先創建變量x的引用並開闢一個存放字面值1的地址,x指向這塊地址;處理int y=1時,先創建y的引用變量後,發現棧中已有字面值1,直接將y指向1的地址即可。
2、對於包裝類數據,如Integer,String,Double等,這些類數據全部存於堆中,Java用new語句來顯示地告訴編譯器,在運行時纔會跟局需要動態創建。
對於自動拆箱、裝箱需要注意:把int變成Integer的時候,如果int那隻在-128~127之間,返回的並不是新new出來的Integer對象,而是一個已緩存在堆中的Integer對象;
Integer c = 127;
Integer d = 127;
Integer e = 128;
Integer f = 128; //編譯器後臺進行Integer f = new Integer(128)的轉換
System.out.println(c==d); //true
System.out.println(e==f); //false
3、字符串包裝類數據(String):
3.1 String str = "asd"創建過程:
a.首先在常量池中查找是否有“asd”字符串對象
b1.若有直接讓str引用該對象
b2.若無則在常量池中創建“asd”,並讓str引用該對象
常量池屬於類信息的一部分,而類信息對應存在於JVM內存模型的方法區,而方法區是在JVM內存模型中的堆中有JVM來分配。所以“asd”可以說存在於堆中(不過,爲了把方法區的堆區別於JVM的堆,有些資料會把方法區稱爲棧)。“asd”在編譯時會被寫入字節碼中,當class文件被加載時,JVM就爲“asd”在常量池中分配內存,所以和靜態區差不多(靜態區是什麼)
3.2 String str = new String ("asd")的創建過程
a.首先在堆中(不是常量池)創建對象“asd”,並讓str引用指向該對象
b.在字符串常量池中查看是否存在內容爲“asd”的字符串對象
c1.若存在,則將new出來的字符串對象與字符串常量池中的對象聯繫起來(什麼叫聯繫起來)
c2.若不存在,則在字符串常量池中創建內容爲“asd”的字符串對象,並將堆中的對象與之聯繫起來
關注intern()方法,返回該字符串在常量池中的對象的引用。