在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當該變量退出該做用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做他用。 面試
堆內存用來存放由new建立的對象和數組。 在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。在堆中產生了一個數組或對象後,還能夠 在棧中定義一個特殊的變量,讓棧中這個變量的取值等於數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。引用變量就至關因而爲數組或對象起的一個名稱,之後就能夠在程序中使用棧中的引用變量來訪問堆中的數組或對象。引用變量就至關因而爲數組或者對象起的一個名稱。數組
常量池指的是在編譯期被肯定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各類基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,好比: app
虛擬機必須爲每一個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其餘類型,字段和方法的符號引用。函數
對於String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的, 對於String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值,注意:該表只存儲文字字符串值,不存儲符號引 用。說到這裏,對常量池中的字符串值的存儲位置應該有一個比較明瞭的理解了。優化
在程序執行的時候,常量池會儲存在Method Area,而不是堆中。spa
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象經過new、newarray、 anewarray和multianewarray等指令創建,它們不須要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優點是能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,由於它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些再也不使用的數據。但缺點是,因爲要在運行時動態 分配內存,存取速度較慢。 code
棧的優點是,存取速度比堆要快,僅次於寄存器,棧數據能夠共享。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。棧中主要存放一些基本類型的變量數據(int, short, long, byte, float, double, boolean, char)和對象句柄(引用)。對象
對於字符串,其對象的引用都是存儲在棧中的,若是是編譯期已經建立好(直接用雙引號定義的)的就存儲在常量池中,若是是運行期(new出來的)才能肯定的就存儲在堆中。對於equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。blog
這裏咱們主要關心棧,堆和常量池,對於棧和常量池中的對象能夠共享,對於堆中的對象不能夠共享。棧中的數據大小和生命週期是能夠肯定的,當沒有引用指向數據時,這個數據就會消失。堆中的對象的由垃圾回收器負責回收,所以大小和生命週期不須要肯定,具備很大的靈活性。 接口
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爲成員變量。下面分析一下代碼執行時候的變化: