方法區
線程共享。
當JVM使用類裝載器裝載某個類時,首先獲取class文件,提取該文件的內容信息,將這些信息存儲到方法區,最後返回一個class實例。方法區用於存儲已經被虛擬機加載的類信息(class)(版本,字段,方法,接口等描述信息),常量,靜態變量(static),即時編譯期編譯後的代碼數據等。稱爲“永久代”。GC在這區域較少出現,內存回收的主要目標是針對常量池的回收和對類的卸載(某個類不再使用)。
運行時常量池
是方法區的一部分,class文件中除了有類的版本,字段,方法,接口等描述信息以外,還有一項信息是常量池。
常量池:用於存放編譯期生成的各種字面量(final修飾)和符號引用,這部分內容將在類加載後進入方法區的運行時常量池中存放。運行時常量池對於class文件常量池具備動態性,在程序運行期間也可以將新常量放入池中。(如final修飾的變量,可以不在聲明時賦值,在運行時動態賦值,但是一旦賦值後不能改變)
String類型是由final修飾的,它是常量池中最常見的一種類型
在JAVA虛擬機(JVM)中存在着一個字符串池,其中保存着很多String對象,並且可以被共享使用,因此它提高了效率。由於String類是final的,它的值一經創建就不可改變,因此我們不用擔心String對象共享而帶來程序的混亂。字符串池由String類維護,我們可以調用intern()方法來訪問字符串池。
String str = "helloword";//str指向常量池(字符串池)中的“helloworld”
String str1 = new String("helloword");//在堆中創建新對象,str1指向堆中對象
System.out.println(str==str1);// 運行後結果爲false
//str1在new的過程中放在了堆引用,而str是放在常量池中的引用,
// 而’==’對比的是引用信息,所以是false.
System.out.println(str==str1.intern());//運行結果是true
//str1.intern()這個方法是一個native方法,
// 它是看String常量池中有沒有這個引用如果有,則返回這個常量池引用,
// 如果沒有則把它放到常量池中.
其他的幾個例子
String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
分析:JVM對於字符串常量的"+"號連接,將程序編譯期,JVM就將常量字符串的"+"連接優化爲連接後的值,拿"a" + 1來說,經編譯器優化後在class中就已經是a1。在編譯期其字符串常量的值就確定下來,故上面程序最終的結果都爲true。
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
分析:JVM對於字符串引用,由於在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + bb無法被編譯器優化,只有在程序運行期來動態分配並將連接後的新地址賦給b。所以上面程序的結果也就爲false。
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
分析:和[2]中唯一不同的是bb字符串加了final修飾,對於final修飾的變量,它在編譯時被解析爲常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程序的結果爲true。