寄存器:最快的存儲區,位于不同于其他存儲區的地方——處理器內部。寄存器的數量極其有限,所以寄存器由編譯器根據需求 進行分配。你不能直接控制,也不能在程序中感覺到寄存器存在的任何跡象。
棧:存放基本類型的數據和對象的引用,但對象本身不存放在棧中,而是存放在堆中
堆:一種通用性的內存池(也存在于RAM中),用于存放所以的JAVA對象。堆不同于堆棧的好處是:編譯器不需要知道要從堆里分配多少存儲區域,也不必知道存儲的數據在堆里存活多長時間。因此,在堆里分配存儲有很大的靈活性。當你需要創建一個對象的時候,只需要new寫一行簡單的代碼,當執行這行代碼時,會自動在堆里進行存儲分配。當然,為這種靈活性必須要付出相應的代碼。用堆進行存儲分配比用堆棧進行存儲存儲需要更多的時間。存放用new產生的數據
靜態域:存放在對象中用static定義的靜態成員
常量池:存放常量
非RAM(隨機存取存儲器)存儲:硬盤等永久存儲空間。如果數據完全存活于程序之外,那么它可以不受程序的任何控制,在程序沒有運行時也可以存在。 這里我們主要關心棧,堆和常量池,對于棧和常量池中的對象可以共享,對于堆中的對象不可以共享。棧中的數據大小和生命周期是可以確定的,當沒有引用指向數據時,這個數據就會消失。堆中的對象的由垃圾回收器負責回收,因此大小和生命周期不需要確定,具有很大的靈活性。
Java內存分配中的棧
在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。當在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內存空間,當該變量退出該作用域后,Java會自動釋放掉為該變量所分配的內存空間,該內存空間可以立即被另作他用。
Java內存分配中的堆
堆內存用來存放由new創建的對象和數組。 在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。在堆中產生了一個數組或對象后,還可以 在棧中定義一個特殊的變量,讓棧中這個變量的取值等于數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。引用變量就相當于是為數組或對象起的一個名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。引用變量就相當于是為數組或者對象起的一個名稱。
常量池 (constant pool)
常量池指的是在編譯期被確定,并被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的*常量值(final)*還包含一些以文本形式出現的符號引用,比如:
類和接口的全限定名;
字段的名稱和描述符;
方法和名稱和描述符。
虛擬機必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他類型,字段和方法的符號引用。
對于String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的, 對于String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值,注意:該表只存儲文字字符串值,不存儲符號引 用。說到這里,對常量池中的字符串值的存儲位置應該有一個比較明了的理解了。
在程序執行的時候,常量池會儲存在Method Area,而不是堆中。
堆與棧
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、 anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的*優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的*,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由于要在運行時動態 分配內存,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量數據(int, short, long, byte, float, double, boolean, char)和對象句柄(引用)。
字符串內存分配
對于字符串,其對象的引用都是存儲在棧中的,如果是編譯期已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(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");
這里解釋一下黃色這3個箭頭,對于通過new產生一個字符串(假設為“china”)時,會先去常量池中查找是否已經有了“china”對象,如果沒有則在常量池中創建一個此字符串對象,然后堆中再創建一個常量池中此”china”對象的拷貝對象。
這也就是有道面試題:Strings=newString(“xyz”);產生幾個對象?一個或兩個,如果常量池中原來沒有”xyz”,就是兩個。
存在于.class文件中的常量池,在運行期被JVM裝載,并且可以擴充。String的 intern()方法就是擴充常量池的 一個方法;當一個String實例str調用intern()方法時,Java 查找常量池中是否有相同Unicode的字符串常量,如果有,則返回其的引用,如果沒有,則在常量池中增加一個Unicode等于str的字符串并返回它的引用
String s0= "kvill";
String s1=new String("kvill");
String s2=new String("kvill");
System.out.println( s0==s1 );
s1.intern();
s2=s2.intern(); //把常量池中"kvill"的引用賦給s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );false
false
true
true
String常量池問題的幾個例子:
【1】
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
【2】
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
【3】
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static String getBB() {
return "b";
}
分析:
【1】中,JVM對于字符串引用,由于在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + bb無法被編譯器優化,只有在程序運行期來動態分配并將連接后的新地址賦給b。所以上面程序的結果也就為false。
【2】和【1】中唯一不同的是bb字符串加了final修飾,對于final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程序的結果為true。
【3】JVM對于字符串引用bb,它的值在編譯期無法確定,只有在程序運行期調用方法后,將方法的返回值和"a"來動態連接并分配地址為b,故上面程序的結果為false。
結論:
字符串是一個特殊包裝類,其引用是存放在棧里的,而對象內容必須根據創建方式不同定(常量池和堆).有的是編譯期就已經創建好,存放在字符串常量池中,而有的是運行時才被創建使用new關鍵字,存放在堆中。
基礎類型的變量和常量在內存中的分配
對于基礎類型的變量和常量,變量和引用存儲在棧中,常量存儲在常量池中。
int i1 = 9;
int i2 = 9;
int i3 = 9;
final int INT1 = 9;
final int INT2 = 9;
final int INT3 = 9;
編譯器先處理int i1 = 9;首先它會在棧中創建一個變量為i1的引用,然后查找棧中是否有9這個值,如果沒找到,就將9存放進來,然后將i1指向9。接著處理int i2 = 9;在創建完i2的引用變量后,因為在棧中已經有9這個值,便將i2直接指向9。這樣,就出現了i1與i2同時均指向9的情況。最后i3也指向這個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 change(int i) {
i = 1234;
}
}
對于以上這段代碼,date為局部變量,i,d,m,y都是形參為局部變量,day,month,year為成員變量。下面分析一下代碼執行時候的變化:
main方法開始執行:int date = 9; date局部變量,基礎類型,引用和值都存在棧中。
Test test = new Test();test為對象引用,存在棧中,對象(new Test())存在堆中。
test.change(date); i為局部變量,引用和值存在棧中。當方法change執行完成后,i就會從棧中消失。
BirthDate d1= new BirthDate(7,7,1970); d1為對象引用,存在棧中,對象(new BirthDate())存在堆中,其中d,m,y為局部變量存儲在棧中,且它們的類型為基礎類型,因此它們的數據也存儲在棧中。day,month,year為成員變量,它們存儲在堆中(new BirthDate()里面)。當BirthDate構造方法執行完之后,d,m,y將從棧中消失。
main方法執行完之后,date變量,test,d1引用將從棧中消失,new Test(), new BirthDate()將等待垃圾回收。