小學徒進階系列—JVM對String的處理

 對於String類型,java官網的文檔是這樣子描述的: html

  http://bbs.itheima.com/thread-23776-1-1.html?fstgjString類表明着字符串。java程序中的全部字符串字面值(如"abc")都做爲此類的實例實現。 java

  字符串是常量,他們的值在建立以後不能更改。由於 String 對象是不可變的,因此能夠共享。 緩存

  那麼,jvm是怎麼共享這些字符串的呢? app

  爲了節省內存,提升資源的複用,jvm引入了常量池這個概念,它屬於方法區的一部分的,做用之一就是存放編譯期間生產的各類字面量和符號引用。而從前面的博文《深刻了解JVM—內存區域》咱們能夠知道,方法區的垃圾回收行爲是比較少出現的,該區中的對象基本不會被回收,能夠理解成是永久存在的。 jvm

  所以,緩存在字符串緩衝區中的字符串對象基本是不被回收的,而jvm也正是經過複用這些對象從而達到共享做用。 學習

  從上一段話中的概念能夠知道,通常狀況下,只有編譯期間能夠肯定下來的的字符串才能存放到緩衝區中。爲何要強調是通常狀況下呢?由於String類爲咱們提供了一個intern()方法,它能夠幫咱們將不存在於緩存池中的java字符串添加到緩存池中,並返回緩存池中該字符串對象的引用。 ui

  具體關於intern()方法,後面咱們再給出代碼作簡單說明吧。如今咱們將重點放在,什麼狀況下可以在編譯期間直接肯定字符串變量值而且將它添加到緩衝區中呢? spa

  若是程序的字符串鏈接表達式中沒有使用變量或者調用方法,那麼該字符串變量的值就可以在編譯期間肯定下來,而且將該字符換緩存在緩衝區中,同時讓該變量指向該字符串;不然將沒法利用緩衝區,由於使用了變量和調用了方法以後的字符串變量的值只能在運行期間才能肯定鏈接式的值,也就沒法在編譯期間肯定字符串變量的值,從而沒法將字符串變量增長到緩衝區並加以利用。 code

下面咱們來看看如何代碼並經過查看他的編譯過程來驗證上述結論吧。 htm

代碼一(沒有使用變量或者調用方法):

複製代碼
 1 package com.xiaoxuetu.string;  2  3 public class Test {  4  5 public static void main(String args[]) {  6 String param1 = "abc";  7 String param2 = "abc" + "def";  8 String param3 = "abcdef";  9  } 10 }
複製代碼

  首先咱們打開cmd.exe, 經過javac Application.java編譯好該Java文件,而後經過命令javap -c Application來查看java編譯後的ByteCode字節碼,如圖所示:

  咱們先來解釋下,ldc的含義是:將常量值從常量池中取出來而且壓入棧中。從上圖中,咱們能夠看到第0行、第3行和第6行中,程序分別從常量池中取出 "abc" 和 "abcdef" 而且壓入棧中,並且,第3行和第6行中的字符串引用是同一個。這說明了,在編譯期間,該字符串變量的值已經肯定了下來,而且將該字符串值緩存在緩衝區中,同時讓該變量指向該字符串值,後面若是有使用相同的字符串值,則繼續指向同一個字符串值。

  若是有安裝jad的話,咱們還能夠經過jad -o -a Test.class命令來生成java代碼和對應java編譯後的ByteCode字節碼一塊兒的jad文件,如圖所示:

 

代碼二(使用了變量或者調用了方法):

複製代碼
1 package com.xiaoxuetu.string; 2 3 public class Application { 4 public static void main(String[] args) { 5 String param = "abc"; 6 String param1 = "3abc"; 7 String param2 = param.length() + "abc"; 8  } 9 }
複製代碼

  一樣,咱們編譯後用jad命令來生成對應的文件查看比較方便吧。

  從上圖中,咱們看到了param的值引用是從常量池中取出的字符串"abc", param1的引用也是直接從常量池中取出的"3abc";可是param2的值並無根據運算結果引用常量池中的「3abc」,而是返回當前StringBuilder對象的引用。這點咱們能夠直接經過 param2 == param1 來判斷,很明顯輸出結果就是false.

  除此以外,咱們還能夠從該圖中的第11行到第19行看出,javas在處理str = str.length() + "def"的時候,是經過StringBuilder實例對象的append()方法來實現的。返回的是StringBuilder對象的引用,因此此時str的值並無引用常量池中緩存的也有的對象。對此,官網文檔是這麼解釋的:

  Java 語言提供對字符串串聯符號("+")以及將其餘對象轉換爲字符串的特殊支持。字符串串聯是經過 StringBuilder(或 StringBuffer)類及其 append 方法實現的。字符串轉換是經過 toString 方法實現的,該方法由 Object 類定義,並可被 Java 中的全部類繼承。有關字符串串聯和轉換的更多信息,請參閱 Gosling、Joy 和 Steele 合著的 The Java Language Specification。

 

或許有人看了之後會有如下疑問:

1> 若是將前面代碼二中的第6 、7行交換,變成以下:

1 String param2 = param.length() + "abc"; 2 String param1 = "3abc";

那麼param2變量的值「3abc」會不會緩存,而後被param1直接取出來使用呢?

答案是不會的,由於param2變量的字符串值必須在運行時才能肯定下來,而不是概念中編譯期間,真正將"3abc"緩存的反而會是param1這行代碼。

 

2>若是咱們經過String newStr = new String("abc");來建立字符串變量,那麼abc會不會被緩存呢?並且會不會直接指向緩衝區中的變量呢?

好吧,咱們繼續看看代碼而後經過查看編譯消息進行分析:

代碼三(使用了變量或者調用了方法):

複製代碼
 1 package com.xiaoxuetu.string;  2  3 public class Application2 {  4 public static void main(String[] args) {  5 String param = "abc";  6 String newStr = new String("cde");  7 String param2 = "cde";  8  9  } 10 }
複製代碼

接着查看jad命令執行後生成的文件:

咱們看到在建立newStr的String類型對象的時候,先從棧中取出字符串"cde",而後調用String的構造方法經過關鍵字new 進行建立對象的建立,將新的引用賦給newStr。所以newStr並無指向緩衝區中的字符串「cde」,因此經過這種方法建立的字符串變量開銷每每比較大。

接下來咱們講解一下intern()方法吧。關於這個方法,官網是這麼描述的:

當調用 intern 方法時,若是池已經包含一個等於此 String 對象的字符串(用 equals(Object) 方法肯定),則返回池中的字符串。不然,將此 String 對象添加到池中,並返回此 String 對象的引用。

下面咱們只給出一個intern()方法使用的例子,具體你們就自行研究咯。

  View Code

 

文筆表達能力有限,可能寫的比較通常。不知道你們看了以後會不會有其餘問題哦,但願你們踊躍提出,共同窗習共同進步。謝謝。

最後就總結一下判斷字符串是否被緩存到緩衝區的兩大要素 :

1>編譯期間 : 也就說字符串鏈接式中沒有使用變量或者調用方法。

2>是否使用了intern()方法 : 使用了該方法的字符串變量的值若是不存在緩衝區中將會被緩存。

 

 

 

轉載請註明出處:http://www.cnblogs.com/xiaoxuetu/  ,謝謝合做

 哈嘍, 你們好! 我是小學徒V。  您的支持是我無限的動力,在此很是感謝您閱讀完本篇文章。

 若是你們以爲我寫的不錯的話,不要忘記動動手指點下左下角的  好文要頂  按鈕哦

 若是你們想繼續關注個人後續博文,能夠經過直接點擊左下角的  關注我  按鈕關注個人最新動態

 若是你們對本文內容存在疑問,能夠直接留下評論,我會及時處理的哦

相關文章
相關標籤/搜索