又拿出這道String str1 = new String("abc");創建幾個對象的面試題梳理了一下常量池與方法區的關系,希望能把這兩者的關系通過這道面試題說明白
方法區是什么
簡單說方法區用於存儲jvm加載的類的信息、常量、靜態變量、編譯后的代碼
方法區、永久代與元空間的關系
下文都以HotSpot來說明
首先方法區是JVM規范的說法,永久代、元空間是HotSpot用來實現方法區的兩個具體的實現
JDK1.8以前使用永久代Perm實現了JVM規范中的方法區
JDK1.8廢棄永久代,變更為元空間,不是廢棄了方法區
永久代與元空間的區別是元空間不在虛擬機內存中,而使用本地內存,目的是為了融合HotSpot與JRockit VM而做出的努力並且減少,並且由於這部分空間的GC效果難以令人滿意
上面我們就說清楚了方法區、永久代與元空間這幾個名詞之間的關系和區別,下面我們在來看三個常量池的關系和不同
常量池
字符串常量池
是一個哈希表(StringTable),里面存的是駐留字符串的引用
字符串駐留:JVM 為了提高性能會將能在編譯時期確定的字符串放在字符串駐留池的內存塊中,String a = "abc"; String b = new String("def");都在編譯時期能確定主流字符串"abc""def"
在堆中的字符串實例被這個哈希表引用之后就等同被賦予了”駐留字符串”的身份
在JVM中字符串常量池被所有類共享
字符串常量池在JDK1.7的版本從永久代移動到了堆
class文件常量池
class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池,用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)。注意這是class文件中的內容
字面量就是我們所說的常量概念,如文本字符串、被聲明為final的常量值等
符號引用是一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可,通常包含:類的全限定名、字段的名稱和描述符、方法的名稱和描述符
總之這些內容能表示這個類的代碼內容
運行時常量池
JDK1.7僅僅把字符串常量池移動到了堆,JDK1.8雖然廢棄了永久代變更為元空間,但是運行時常量池仍然跟隨元空間被移出JVM內存
當類加載到內存中后,jvm就會將class常量池中的內容存放到運行時常量池中,由此可知,運行時常量池也是每個類都有一個
並且在類加載的解析階段會把運行時常量池的符號引用替換成直接引用,這個過程需要查找字符串常量池
由上面的內容我們可知new String對象創建問題只和堆以及堆內的字符串常量池有關系(字符串常量池在不在堆對new String沒什么影響)
1 //在堆中會有一個”abc”實例,全局StringTable中存放着”abc”的一個引用值 2 String str1 = "abc"; 3 //生成兩個實例,一個是”def”的實例對象,並且StringTable中存儲一個”def”的引用值,還有一個是new出來的一個”def”的實例對象(指向str2) 4 String str2 = new String("def"); 5 //查找StringTable,里面有”abc”的全局駐留字符串引用,所以str3的引用地址與之前的那個已存在的相同 6 String str3 = "abc"; 7 //調用intern()函數,返回StringTable中”def”的引用值,如果沒有就將str2的引用值添加進去 8 String str4 = str2.intern(); 9 //最后str5在解析的時候就也是指向存在於StringTable中的”def”的引用值 10 String str5 = "def"; 11 //生成一個實例,new出來的一個”def”的實例對象(指向str6),(def母本與StringTable放入”def”引用值已經創建) 12 String str6 = new String("def"); 13 14 System.out.println(str1 == str3);//true 15 16 System.out.println(str2 == str4);//false 17 System.out.println(str4 == str5);//true 18 19 System.out.println(str5 == str2);//false 20 System.out.println(str6 == str2);//false 21 22 System.out.println(str6 == str2.intern());//false 23 System.out.println(str6.intern() == str2.intern());//true