本文目錄一覽:
JVM中常量池存放在哪裡
java8之前:
java8之後:元數據區 Metaspace
由於 PermGen 內存管理的效果遠沒有達到預期,所以JCP已經着手去除PermGen的工作。在JDK7中,字符串常量已經從永久代移除。現今 JDK8 中 PermGen 已經被徹底移除,取而代之的是metaspace數據區,使用native內存,申請和釋放由虛擬機負責管理。
那麼,JVM中常量池到底存放在哪裡?
Java6和6之前,常量池是存放在方法區(永久代)中的。
Java7,將常量池是存放到了堆中。
Java8之後,取消了整個永久代區域,取而代之的是元空間。 運行時常量池和靜態常量池存放在元空間中,而字符串常量池依然存放在堆中。
java 常量池 到底是在堆中還是棧中?
java常量池不在堆中也不在棧中,是獨立的內存空間管理。
1. 棧:存放基本類型的變量數據和對象的引用,但對象本身不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象存放在常量池中。)
2. 堆:存放所有new出來的對象。
3. 常量池:存放字符串常量和基本類型常量(public static final)。
對於字符串:其對象的引用都是存儲在棧中的,如果是編譯期已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才能確定的就存儲在堆中。對於equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。
Java運行時常量池是什麼?
在class文件中,“常量池”是最複雜也最值得關注的內容。
Java是一種動態連接的語言,常量池的作用非常重要,常量池中除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值還,還包含一些以文本形式出現的符號引用,比如:
類和接口的全限定名;
字段的名稱和描述符;
方法和名稱和描述符。
在C語言中,如果一個程序要調用其它庫中的函數,在連接時,該函數在庫中的位置(即相對於庫文件開頭的偏移量)會被寫在程序中,在運行時,直接去這個地址調用函數;
而在Java語言中不是這樣,一切都是動態的。編譯時,如果發現對其它類方法的調用或者對其它類字段的引用的話,記錄進class文件中的,只能是一個文本形式的符號引用,在連接過程中,虛擬機根據這個文本信息去查找對應的方法或字段。
所以,與Java語言中的所謂“常量”不同,class文件中的“常量”內容很非富,這些常量集中在class中的一個區域存放,一個緊接着一個,這裡就稱為“常量池”。
java中的常量池技術,是為了方便快捷地創建某些對象而出現的,當需要一個對象時,就可以從池中取一個出來(如果池中沒有則創建一個),則在需要重複重複創建相等變量時節省了很多時間。常量池其實也就是一個內存空間,不同於使用new關鍵字創建的對象所在的堆空間。本文只從java使用者的角度來探討java常量池技術,並不涉及常量池的原理及實現方法。個人認為,如果是真的專註java,就必須對這些細節方面有一定的了解。但知道它的原理和具體的實現方法則不是必須的。
常量池中對象和堆中的對象
[java] view plain copy
public class Test{
Integer i1=new Integer(1);
Integer i2=new Integer(1);
//i1,i2分別位於堆中不同的內存空間
System.out.println(i1==i2);//輸出false
Integer i3=1;
Integer i4=1;
//i3,i4指向常量池中同一個內存空間
System.out.println(i3==i4);//輸出true
//很顯然,i1,i3位於不同的內存空間
System.out.println(i1==i3);//輸出false
}
8種基本類型的包裝類和對象池
java中基本類型的包裝類的大部分都實現了常量池技術,這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點數類型的包裝類則沒有實現。另外Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應值小於等於127時才可使用對象池,也即對象不負責創建和管理大於127的這些類的對象。以下是一些對應的測試代碼:
[java] view plain copy
public class Test{
public static void main(String[] args){
//5種整形的包裝類Byte,Short,Integer,Long,Character的對象,
//在值小於127時可以使用常量池
Integer i1=127;
Integer i2=127;
System.out.println(i1==i2)//輸出true
//值大於127時,不會從常量池中取對象
Integer i3=128;
Integer i4=128;
System.out.println(i3==i4)//輸出false
//Boolean類也實現了常量池技術
Boolean bool1=true;
Boolean bool2=true;
System.out.println(bool1==bool2);//輸出true
//浮點類型的包裝類沒有實現常量池技術
Double d1=1.0;
Double d2=1.0;
System.out.println(d1==d2)//輸出false
}
}
String也實現了常量池技術
String類也是java中用得多的類,同樣為了創建String對象的方便,也實現了常量池的技術,測試代碼如下:
[java] view plain copy
public class Test{
public static void main(String[] args){
//s1,s2分別位於堆中不同空間
String s1=new String(“hello”);
String s2=new String(“hello”);
System.out.println(s1==s2)//輸出false
//s3,s4位於池中同一空間
String s3=”hello”;
String s4=”hello”;
System.out.println(s3==s4);//輸出true
}
}
最後
細節決定成敗,寫代碼更是如此。
在JDK5.0之前是不允許直接將基本數據類型的數據直接賦值給其對應地包裝類的,如:Integer i = 5;
但是在JDK5.0中支持這種寫法,因為編譯器會自動將上面的代碼轉換成如下代碼:Integer i=Integer.valueOf(5);
這就是Java的裝箱.JDK5.0也提供了自動拆箱. Integer i =5; int j = i;
Integer的封裝:
[java] view plain copy
public static Integer valueOf(int i) {
final int offset = 128;
if (i = -128 i = 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
private static class IntegerCache {
private IntegerCache(){}
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for(int i = 0; i cache.length; i++)
cache[i] = new Integer(i – 128);
}
}
由於cache[]在IntegerCache類中是靜態數組,也就是只需要初始化一次,即static{……}部分,所以,如果Integer對象初始化時是-128~127的範圍,就不需要再重新定義申請空間,都是同一個對象—在IntegerCache.cache中,這樣可以在一定程度上提高效率。
Java中的幾種常量池
字符串常量池:當類加載完成,在堆中生成字符串對象實例,然後將該字符串對象實例的引用值存到string pool中。
class文件常量池:用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)。
運行時常量池:當類加載到內存中後,jvm就會將class常量池中的內容存放到運行時常量池中。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/238399.html