1. string常量問題
1.1. 運行時常量池與Class文件常量池區別
JVM對Class文件中每一部分的格式都有嚴格的要求,每一個字節用於存儲那種數據都必須符合規範上的要求才會被虛擬機認可、裝載和執行;但運行時常量池沒有這些限制,除了保存Class文件中描述的符號引用,還會把翻譯出來的直接引用也存儲在運行時常量區
java代碼編譯後,程序會先被編譯爲.class文件,編譯後的字節碼文件格式主要分爲兩部分:常量池和方法字節碼。
常量池記錄的是代碼出現過的所有token(類名,成員變量名等等)以及符號引用(方法引用,成員變量引用等等);方法字節碼放的是類中各個方法的字節碼。
相較於Class文件常量池,運行時常量池更具動態性,在運行期間也可以將新的變量放入常量池中,而不是一定要在編譯時確定的常量才能放入。最主要的運用便是String類的intern()方法.
1.2. String.intern()
檢查字符串常量池中是否存在String並返回池裏的字符串引用
(1) 若池中存在,那麼直接返回池中它的引用;
(2) 若池中不存在,則將其加入池中,並返回其引用。
這樣做主要是爲了避免在堆中不斷地創建新的字符串對象
代碼1:
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); //flase
String temp=s.intern();
System.out.println(temp == s2); //true
String s3 = new String("1") + new String("1");
s3.intern();//池中未發現"11",將"11"的引用s3放置在池中,並返回s3引用
String s4 = "11";
System.out.println(s3 == s4); //true
}
注意:雖然String.intern()的返回值永遠等於字符串常量。但這並不代表在系統的每時每刻,相同的字符串的intern()返回都會是一樣的(雖然在95%以上的情況下,都是相同的)。因爲存在這麼一種可能:在一次intern()調用之後,該字符串在某一個時刻被回收,之後,再進行一次intern()調用,那麼字面量相同的字符串重新被加入常量池,但是引用位置已經不同。
1.3. final修飾的字符串
代碼1:
public static void main(String[] args) {
final String a = "hello";
String b = "hello";
final String c = "world";
String d = "hello" + "world";
String e = a + c;
String f = b + c;
String g = "helloworld";
System.out.println(g == d);//true
System.out.println(g == e);//true
System.out.println(a == b);//true
System.out.println(g == f);//false
}
由於final修飾的String變量不可更改,所以,當一個String變量被final修飾時,這個值在編譯期就可以確定,所有將該變量直接替換爲它對應的值.
在編譯期,由於a和c的值已經確定並且不會再更改(效果同d),所以e的值能夠在編譯期就確定下來,直接指向了常量區的g,前兩個均爲true;再看下f,由於b值(變量)的不確定性,所以在編譯期不能確定其值,只能在運行時確認,所以(g == f)爲false。
1.4. 在字符串常量池中的優化
代碼1:
public static String str = "laji" + "MySQL";
public static void main(){
}
代碼2:
public static void main(String[] args){
String string1 = "hello";
String string2 = "world";
String string3 = string1+string2;
String string4="helloworld";
System.out.println(string3 == string4);//false
}
是不是發現和上邊小結代碼很相似,只是其中的字符串變量沒有被final修飾,出現不同的結果證明了final修飾變量時的作用:變量不可變(被final修飾的變量其實就是常量)
總結:
-
對於直接做+運算的兩個字符串(字面量)常量,並不會放入String常量池中,而是直接把運算後的結果放入常量池中.
-
對於先聲明的字符串字面量常量,會放入常量池,但是若使用字面量的引用進行運算就不會把運算後的結果放入常量池中
-
總結一下就是JVM會對String常量的運算進行優化,未聲明的,只放結果;已經聲明的,只放聲明
1.5. 字符串常量池在JVM運行時數據區的什麼位置
方法區 ,方法區域在邏輯上是堆的一部分
代碼1
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
出現異常,heap space
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.lang.Integer.toString(Integer.java:440)
at java.base/java.lang.String.valueOf(String.java:3058)
at com.example.demo.util.Test2.main(Test2.java:11)