請別再拿“String s = new String("xyz");創建了多少個String實例”來面試了吧

這帖是用來回復高級語言虛擬機圈子裏的一個問題,一道Java筆試題的。
本來因爲見得太多已經吐槽無力,但這次實在忍不住了就又爆發了一把。寫得太長乾脆單獨開了一帖。

順帶廣告:對JVM感興趣的同學們同志們請多多支持高級語言虛擬機圈子 

以下是回覆內容。文中的“樓主”是針對原問題帖而言。

===============================================================

樓主是看各種寶典了麼……以後我面試人的時候就要專找寶典答案是錯的來問,方便篩人orz

樓主要注意了:這題或類似的題雖然經常見,但使用這個描述方式實際上沒有任何意義:
引用
問題:
Java代碼  收藏代碼
  1. String s = new String("xyz");  
創建了幾個String Object?

這個問題自身就沒有合理的答案,樓主所引用的“標準答案”自然也就不準確了:
引用
答案:兩個(一個是“xyz”,一個是指向“xyz”的引用對象s)

(好吧這個答案的吐槽點很多……大家慢慢來)

這問題的毛病是什麼呢?它並沒有定義“創建了”的意義。
什麼叫“創建了”?什麼時候創建了什麼?
而且這段Java代碼片段實際運行的時候真的會“創建兩個String實例”麼?

如果這道是面試題,那麼可以當面讓面試官澄清“創建了”的定義,然後再對應的回答。這種時候面試官多半會讓被面試者自己解釋,那就好辦了,好好曬給面試官看。

如果是筆試題就沒有提問要求澄清的機會了。不過會出這種題目的地方水平多半也不怎麼樣。說不定出題的人就是從各種寶典上把題抄來的,那就按照寶典把那不大對勁的答案寫上去就能混過去了

===============================================================

先換成另一個問題來問:
引用
問題:
Java代碼  收藏代碼
  1. String s = new String("xyz");  
在運行時涉及幾個String實例?

一種合理的解答是:
引用
答案:兩個,一個是字符串字面量"xyz"所對應的、駐留(intern)在一個全局共享的字符串常量池中的實例,另一個是通過new String(String)創建並初始化的、內容與"xyz"相同的實例

這是根據Java語言規範相關規定可以給出的合理答案。考慮到Java語言規範中明確指出了:
The Java Language Specification, Third Edition 寫道
The Java programming language is normally compiled to the bytecoded instruction set and binary format defined in The Java Virtual Machine Specification, Second Edition (Addison-Wesley, 1999).

也就是規定了Java語言一般是編譯爲Java虛擬機規範所定義的Class文件,但並沒有規定“一定”(must),留有不使用JVM來實現Java語言的餘地。
考慮上Java虛擬機規範,確實在這段代碼裏涉及的常量種類爲CONSTANT_String_info的字符串常量也只有"xyz"一個。CONSTANT_String_info是用來表示Java語言中String類型的常量表達式的值(包括字符串字面量)用的常量種類,只在這個層面上考慮的話,這個解答也沒問題。
所以這種解答可以認爲是合理的。

值得注意的是問題中“在運行時”既包括類加載階段,也包括這個代碼片段自身執行的時候。下文會再討論這個細節與樓主原本給出的問題的關係。

碰到這種問題首先應該想到去查閱相關的規範,這裏具體是Java語言規範Java虛擬機規範,以及一些相關API的JavaDoc。很多人喜歡把“按道理說”當作口頭禪,規範就是用來定義各種“道理”的——“爲什麼XXX是YYY的意思?”“因爲規範裏是這樣定義的!”——無敵了。

在Java虛擬機規範中相關的定義有下面一些:

The Java Virtual Machine Specification, Second Edition 寫道
2.3 Literals

A literal is the source code representation of a value of a primitive type (§2.4.1), the String type (§2.4.8), or the null type (§2.4). String literals and, more generally, strings that are the values of constant expressions are "interned" so as to share unique instances, using the method String.intern.

The null type has one value, the null reference, denoted by the literal null. The boolean type has two values, denoted by the literals true and false.

2.4.8 The Class String

Instances of class String represent sequences of Unicode characters (§2.1). A String object has a constant, unchanging value. String literals (§2.3) are references to instances of class String.

2.17.6 Creation of New Class Instances

A new class instance is explicitly created when one of the following situations occurs:

  • Evaluation of a class instance creation expression creates a new instance of the class whose name appears in the expression.
  • Invocation of the newInstance method of class Class creates a new instance of the class represented by the Class object for which the method was invoked.

A new class instance may be implicitly created in the following situations:

  • Loading of a class or interface that contains a String literal may create a new String object (§2.4.8) to represent that literal. This may not occur if the a String object has already been created to represent a previous occurrence of that literal, or if the String.intern method has been invoked on a String object representing the same string as the literal.
  • Execution of a string concatenation operator that is not part of a constant expression sometimes creates a new String object to represent the result. String concatenation operators may also create temporary wrapper objects for a value of a primitive type (§2.4.1).

Each of these situations identifies a particular constructor to be called with specified arguments (possibly none) as part of the class instance creation process.

5.1 The Runtime Constant Pool

...

● A string literal (§2.3) is derived from a CONSTANT_String_info structure (§4.4.3) in the binary representation of a class or interface. The CONSTANT_String_info structure gives the sequence of Unicode characters constituting the string literal.

● The Java programming language requires that identical string literals (that is, literals that contain the same sequence of characters) must refer to the same instance of class String. In addition, if the method String.intern is called on any string, the result is a reference to the same class instance that would be returned if that string appeared as a literal. Thus,

Java代碼  收藏代碼
  1. ("a" + "b" + "c").intern() == "abc"  

must have the value true.

● To derive a string literal, the Java virtual machine examines the sequence of characters given by the CONSTANT_String_info structure.

  ○ If the method String.intern has previously been called on an instance of class String containing a sequence of Unicode characters identical to that given by the CONSTANT_String_info structure, then the result of string literal derivation is a reference to that same instance of class String.

  ○ Otherwise, a new instance of class String is created containing the sequence of Unicode characters given by the CONSTANT_String_info structure; that class instance is the result of string literal derivation. Finally, the intern method of the new String instance is invoked.


...

The remaining structures in the constant_pool table of the binary representation of a class or interface, the CONSTANT_NameAndType_info (§4.4.6) and CONSTANT_Utf8_info (§4.4.7) structures are only used indirectly when deriving symbolic references to classes, interfaces, methods, and fields, and when deriving string literals.


把Sun的JDK看作參考實現(reference implementation, RI),其中String.intern()的JavaDoc爲:
JavaDoc 寫道
public String intern()

    Returns a canonical representation for the string object.

    A pool of strings, initially empty, is maintained privately by the class String.

    When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

    It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

    All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the Java Language Specification

    Returns:
        a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.


===============================================================

再換一個問題來問:
引用
問題:
Java代碼  收藏代碼
  1. String s = new String("xyz");  
涉及用戶聲明的幾個String類型的變量?

答案也很簡單:
引用
答案:一個,就是String s。

把問題換成下面這個版本,答案也一樣:
引用
問題:
Java代碼  收藏代碼
  1. String s = null;  
涉及用戶聲明的幾個String類型的變量?

Java裏變量就是變量,引用類型的變量只是對某個對象實例或者null的引用,不是實例本身。聲明變量的個數跟創建實例的個數沒有必然關係,像是說:
Java代碼  收藏代碼
  1. String s1 = "a";  
  2. String s2 = s1.concat("");  
  3. String s3 = null;  
  4. new String(s1);  

這段代碼會涉及3個String類型的變量,
1、s1,指向下面String實例的1
2、s2,指向與s1相同
3、s3,值爲null,不指向任何實例

以及3個String實例,
1、"a"字面量對應的駐留的字符串常量的String實例
2、""字面量對應的駐留的字符串常量的String實例
String.concat()是個有趣的方法,當發現傳入的參數是空字符串時會返回this,所以這裏不會額外創建新的String實例)
3、通過new String(String)創建的新String實例;沒有任何變量指向它。

===============================================================

回到樓主開頭引用的問題與“標準答案”
引用
問題:
Java代碼  收藏代碼
  1. String s = new String("xyz");  
創建了幾個String Object?
答案:兩個(一個是“xyz”,一個是指向“xyz”的引用對象s)

用歸謬法論證。假定問題問的是“在執行這段代碼片段時創建了幾個String實例”。如果“標準答案”是正確的,那麼下面的代碼片段在執行時就應該創建4個String實例了:
Java代碼  收藏代碼
  1. String s1 = new String("xyz");  
  2. String s2 = new String("xyz");  

馬上就會有人跳出來說上下兩個"xyz"字面量都是引用了同一個String對象,所以不應該是創建了4個對象。

那麼應該是多少個?

運行時的類加載過程與實際執行某個代碼片段,兩者必須分開討論纔有那麼點意義。

爲了執行問題中的代碼片段,其所在的類必然要先被加載,而且同一個類最多隻會被加載一次(要注意對JVM來說“同一個類”並不是類的全限定名相同就足夠了,而是<類全限定名, 定義類加載器>一對都相同才行)。

根據上文引用的規範的內容,符合規範的JVM實現應該在類加載的過程中創建並駐留一個String實例作爲常量來對應"xyz"字面量;具體是在類加載的resolve階段進行的。這個常量是全局共享的,只在先前尚未有內容相同的字符串駐留過的前提下才需要創建新的String實例。

等到真正執行原問題中的代碼片段時,JVM需要執行的字節碼類似這樣:
Java bytecode代碼  收藏代碼
  1. 0: new  #2; //class java/lang/String  
  2. 3: dup  
  3. 4: ldc  #3; //String xyz  
  4. 6: invokespecial    #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V  
  5. 9: astore_1  

這之中出現過多少次new java/lang/String就是創建了多少個String對象。也就是說原問題中的代碼在每執行一次只會新創建一個String實例。
這裏,ldc指令只是把先前在類加載過程中已經創建好的一個String對象("xyz")的一個引用壓到操作數棧頂而已,並不新創建String對象。

所以剛纔用於歸謬的代碼片段:
Java代碼  收藏代碼
  1. String s1 = new String("xyz");  
  2. String s2 = new String("xyz");  

每執行一次只會新創建2個String實例。

---------------------------------------------------------------

爲了避免一些同學犯糊塗,再強調一次:

在Java語言裏,“new”表達式是負責創建實例的,其中會調用構造器去對實例做初始化;構造器自身的返回值類型是void,並不是“構造器返回了新創建的對象的引用”,而是new表達式的值是新創建的對象的引用。

對應的,在JVM裏,“new”字節碼指令只負責把實例創建出來(包括分配空間、設定類型、所有字段設置默認值等工作),並且把指向新創建對象的引用壓到操作數棧頂。此時該引用還不能直接使用,處於未初始化狀態(uninitialized);如果某方法a含有代碼試圖通過未初始化狀態的引用來調用任何實例方法,那麼方法a會通不過JVM的字節碼校驗,從而被JVM拒絕執行。
能對未初始化狀態的引用做的唯一一種事情就是通過它調用實例構造器,在Class文件層面表現爲特殊初始化方法“<init>”。實際調用的指令是invokespecial,而在實際調用前要把需要的參數按順序壓到操作數棧上。在上面的字節碼例子中,壓參數的指令包括dup和ldc兩條,分別把隱藏參數(新創建的實例的引用,對於實例構造器來說就是“this”)與顯式聲明的第一個實際參數("xyz"常量的引用)壓到操作數棧上。
在構造器返回之後,新創建的實例的引用就可以正常使用了。

關於構造器的討論,可以參考我之前的一帖,實例構造器是不是靜態方法?

===============================================================

以上討論都只是針對規範所定義的Java語言與Java虛擬機而言。概念上是如此,但實際的JVM實現可以做得更優化,原問題中的代碼片段有可能在實際執行的時候一個String實例也不會完整創建(沒有分配空間)。

例如說,在x86、Windows Vista SP2、Sun JDK 6 update 14的fastdebug版上跑下面的測試代碼:
Java代碼  收藏代碼
  1. public class C2EscapeAnalysisDemo {  
  2.   private static void warmUp() {  
  3.     IFoo[] array = new IFoo[] {  
  4.       new FooA(), new FooB(), new FooC(), new FooD()  
  5.     };  
  6.     for (int i = 0; i < 1000000; i++) {  
  7.       array[i % array.length].foo(); // megamorphic callsite to prevent inlining  
  8.     }  
  9.   }  
  10.     
  11.   public static void main(String[] args) {  
  12.     while (true) {  
  13.       warmUp();  
  14.     }  
  15.   }  
  16. }  
  17.   
  18. interface IFoo {  
  19.   void foo();  
  20. }  
  21.   
  22. class FooA implements IFoo {  
  23.   public void foo() {  
  24.     String s1 = new String("xyz");  
  25.   }  
  26. }  
  27.   
  28. class FooB implements IFoo {  
  29.   public void foo() {  
  30.     String s1 = new String("xyz");  
  31.     String s2 = new String("xyz");  
  32.   }  
  33. }  
  34.   
  35. class FooC implements IFoo {  
  36.   public void foo() {  
  37.     String s1 = new String("xyz");  
  38.     String s2 = new String("xyz");  
  39.     String s3 = new String("xyz");  
  40.   }  
  41. }  
  42.   
  43. class FooD implements IFoo {  
  44.   public void foo() {  
  45.     String s1 = new String("xyz");  
  46.     String s2 = new String("xyz");  
  47.     String s3 = new String("xyz");  
  48.     String s4 = new String("xyz");  
  49.   }  
  50. }  

照常用javac用默認參數編譯,然後先用server模式的默認配置來跑,順帶打出GC和JIT編譯日誌來看
Command prompt代碼  收藏代碼
  1. java -server -verbose:gc -XX:+PrintCompilation C2EscapeAnalysisDemo  

看到的日誌的開頭一段如下:
Hotspot log代碼  收藏代碼
  1.   1       java.lang.String::charAt (33 bytes)  
  2.   2       java.lang.Object::<init> (1 bytes)  
  3.   3       java.lang.String::<init> (61 bytes)  
  4.   1%      C2EscapeAnalysisDemo::warmUp @ 47 (71 bytes)  
  5.   4       FooA::foo (11 bytes)  
  6.   5       FooB::foo (21 bytes)  
  7.   6       FooC::foo (31 bytes)  
  8.   7       FooD::foo (42 bytes)  
  9. [GC 3072K->168K(32768K), 0.0058325 secs]  
  10. [GC 3240K->160K(32768K), 0.0104623 secs]  
  11. [GC 3232K->160K(32768K), 0.0027323 secs]  
  12. [GC 3232K->160K(35840K), 0.0026220 secs]  
  13. [GC 6304K->160K(35840K), 0.0173733 secs]  
  14. [GC 6304K->144K(41664K), 0.0059720 secs]  
  15. [GC 12432K->144K(41664K), 0.0353320 secs]  
  16. [GC 12432K->144K(54016K), 0.0139333 secs]  
  17.   8       C2EscapeAnalysisDemo::warmUp (71 bytes)  
  18. [GC 24720K->160K(54016K), 0.0697970 secs]  
  19. [GC 24736K->160K(68800K), 0.0261921 secs]  
  20. [GC 39520K->160K(68800K), 0.0958433 secs]  
  21. [GC 39520K->160K(87168K), 0.0433377 secs]  
  22. [GC 57888K->160K(87168K), 0.0542482 secs]  
  23. [GC 57888K->148K(87168K), 0.0533140 secs]  
  24. [GC 57876K->164K(84288K), 0.0533537 secs]  
  25. [GC 55204K->164K(81728K), 0.0596820 secs]  
  26. [GC 52644K->164K(79488K), 0.0515090 secs]  
  27. [GC 50212K->164K(76992K), 0.0491227 secs]  
  28. [GC 47908K->164K(74944K), 0.0450666 secs]  
  29. [GC 45668K->164K(72640K), 0.0467671 secs]  
  30. [GC 43556K->152K(70784K), 0.0420757 secs]  
  31. [GC 41560K->168K(68736K), 0.0391296 secs]  
  32. [GC 39656K->168K(67072K), 0.0397539 secs]  
  33. [GC 37864K->188K(65216K), 0.0360861 secs]  

上面的日誌中,後面的方法名的行是JIT編譯的日誌,而以[GC開頭的是minor GC的日誌。
程序一直跑,GC的日誌還會不斷的打出來。這是理所當然的對吧?HotSpot的堆就那麼大,而測試代碼在不斷新創建String對象,肯定得不斷觸發GC的。

用不同的VM啓動參數來跑的話,
Command prompt代碼  收藏代碼
  1. java -server -verbose:gc -XX:+PrintCompilation -XX:+DoEscapeAnalysis -XX:+EliminateAllocations C2EscapeAnalysisDemo  

還是同樣的Java測試程序,同樣的Sun JDK 6 update 14,但打開了逃逸分析和空間分配消除功能,再運行,看到的全部日誌如下:
Hotspot log代碼  收藏代碼
  1.   1       java.lang.String::charAt (33 bytes)  
  2.   2       java.lang.Object::<init> (1 bytes)  
  3.   3       java.lang.String::<init> (61 bytes)  
  4.   1%      C2EscapeAnalysisDemo::warmUp @ 47 (71 bytes)  
  5.   5       FooB::foo (21 bytes)  
  6.   4       FooA::foo (11 bytes)  
  7.   6       FooC::foo (31 bytes)  
  8.   7       FooD::foo (42 bytes)  
  9. [GC 3072K->176K(32768K), 0.0056527 secs]  
  10.   8       C2EscapeAnalysisDemo::warmUp (71 bytes)  

繼續跑下去也沒有再打出GC日誌了。難道新創建String對象都不吃內存了麼?

實際情況是:經過HotSpot的server模式編譯器的優化後,FooA、FooB、FooC、FooD四個版本的foo()實現都不新創建String實例了。這樣自然不吃內存,也就不再觸發GC了。
經過的分析和優化籠統說有方法內聯(method inlining)、逃逸分析(escape analysis)、標量替換(scalar replacement)、無用代碼削除(dead-code elimination)之類。

FooA.foo()最短,就以它舉例來大致演示一下優化的過程。
它其實就是創建並初始化了一個String對象而已。調用的構造器的源碼是:
Java代碼  收藏代碼
  1. public String(String original) {  
  2.     int size = original.count;  
  3.     char[] originalValue = original.value;  
  4.     char[] v;  
  5.     if (originalValue.length > size) {  
  6.        // The array representing the String is bigger than the new  
  7.        // String itself.  Perhaps this constructor is being called  
  8.        // in order to trim the baggage, so make a copy of the array.  
  9.       int off = original.offset;  
  10.       v = Arrays.copyOfRange(originalValue, off, off+size);  
  11.     } else {  
  12.        // The array representing the String is the same  
  13.        // size as the String, so no point in making a copy.  
  14.       v = originalValue;  
  15.     }  
  16.     this.offset = 0;  
  17.     this.count = size;  
  18.     this.value = v;  
  19. }  

因爲參數是"xyz",可以確定在我們的測試代碼裏不會走到構造器的if分支裏,下面爲了演示方便就省略掉那部分代碼(實際代碼還是存在的,只是沒執行而已)
Java代碼  收藏代碼
  1. public String(String original) {  
  2.     int size = original.count;  
  3.     char[] originalValue = original.value;  
  4.     char[] v;  
  5.     if (originalValue.length > size) {  
  6.        // 省略  
  7.     } else {  
  8.       v = originalValue;  
  9.     }  
  10.     this.offset = 0;  
  11.     this.count = size;  
  12.     this.value = v;  
  13. }  

那麼把構造器內聯到FooA.foo()裏,
Java代碼  收藏代碼
  1. public FooA implements IFoo {  
  2.   public void foo() {  
  3.     String s = AllocateString(); // 這裏虛構一個只分配空間,不調用構造器的函數  
  4.     String original = "xyz";  
  5.   
  6.     // 下面就是內聯進來的構造器內容  
  7.     int size = original.count;  
  8.     char[] originalValue = original.value;  
  9.     char[] v;  
  10.     if (originalValue.length > size) {  
  11.        // 省略  
  12.     } else {  
  13.       v = originalValue;  
  14.     }  
  15.   
  16.     s.offset = 0;  
  17.     s.count = size;  
  18.     s.value = v;  
  19.   }  
  20. }  


然後經過逃逸分析與標量替換,
Java代碼  收藏代碼
  1. public FooA implements IFoo {  
  2.   public void foo() {  
  3.     String original = "xyz";  
  4.       
  5.     // 下面就是內聯進來的構造器內容  
  6.     int size = original.count;  
  7.     char[] originalValue = original.value;  
  8.     char[] v;  
  9.     if (originalValue.length > size) {  
  10.        // 省略  
  11.     } else {  
  12.       v = originalValue;  
  13.     }  
  14.       
  15.     // 原本s的實例變量被標量替換爲foo()的局部變量  
  16.     int sOffset = 0;  
  17.     int sCount = size;  
  18.     char[] sValue = v;  
  19.   }  
  20. }  

注意,到這裏就已經把新創建String在堆上分配空間的代碼全部削除了,原本新建的String實例的字段變成了FooA.foo()的局部變量。
最後再經過無用代碼削除,把sOffset、sCount和sValue這三個沒被讀過的局部變量給削除掉,
Java代碼  收藏代碼
  1. public FooA implements IFoo {  
  2.   public void foo() {  
  3.     String original = "xyz";  
  4.       
  5.     // 下面就是內聯進來的構造器內容  
  6.     int size = original.count;  
  7.     char[] originalValue = original.value;  
  8.     char[] v;  
  9.     if (originalValue.length > size) {  
  10.        // 省略  
  11.     } else {  
  12.       v = originalValue;  
  13.     }  
  14.       
  15.     // 幾個局部變量也幹掉了  
  16.   }  
  17. }  

這就跟FooA.foo()被優化編譯後實際執行的代碼基本一致了。
實際執行的x86代碼如下:
X86 asm代碼  收藏代碼
  1. 0x0247aeec: mov    %eax,-0x4000(%esp) ; 檢查棧是否溢出(stack bang),若溢出則這條指令會引發異常  
  2. 0x0247aef3: push   %ebp               ; 保存老的棧幀指針  
  3. 0x0247aef4: sub    $0x18,%esp         ; 爲新棧幀分配空間  
  4. 0x0247aefa: mov    $0x1027e9a8,%edi   ; String original /* EDI */ = "xyz"  
  5. 0x0247aeff: mov    0x8(%edi),%ecx     ; char[] originalValue /* ECX */ = original.value;  
  6. 0x0247af02: mov    0x8(%ecx),%ebx     ; EBX = originalValue.length  
  7. 0x0247af05: mov    0x10(%edi),%ebp    ; int size /* EBP */ = original.count;  
  8. 0x0247af08: cmp    %ebp,%ebx          ; 比較originalValue.length與size  
  9. 0x0247af0a: jg     0x0247af17         ; 如果originalValue.length > size則跳轉到0x0247af17  
  10.                                       ;   實際不會發生跳轉(就是不會執行if分支),所以後面代碼省略  
  11. 0x0247af0c: add    $0x18,%esp         ; 撤銷棧幀分配的空間  
  12. 0x0247af0f: pop    %ebp               ; 恢復老的棧幀指針  
  13. 0x0247af10: test   %eax,0x310000      ; 方法返回前檢查是否需要進入safepoint ({poll_return})  
  14. 0x0247af16: ret                       ; 方法返回  
  15. 0x0247af17: ; 以下是if分支和異常處理器的代碼,因爲實際不會執行,省略  

看,確實沒有新創建String對象了。

另外三個版本的foo()實現也是類似,HotSpot成功的把無用的new String("xyz")全部幹掉了。
關於逃逸分析的例子,可以參考我以前一篇帖,HotSpot 17.0-b12的逃逸分析/標量替換的一個演示

再回頭看看樓主的原問題,問題中的代碼片段執行的時候(對應到FooA.foo()被調用的時候)一個String對象也沒有新建。於是那“標準答案”在現實中的指導意義又有多少呢?

===============================================================

另外,樓主還提到了PermGen:
QM42977 寫道
"xyz"在perm gen應該還會生成一個對象,因爲常量("xyz")都會保存在perm gen中

這裏也是需要強調一點:永生代(“Perm Gen”)只是Sun JDK的一個實現細節而已,Java語言規範和Java虛擬機規範都沒有規定必須有“Permanent Generation”這麼一塊空間,甚至沒規定要用什麼GC算法——不用分代式GC算法哪兒來的“永生代”?

HotSpot的PermGen是用來實現Java虛擬機規範中的“方法區”(method area)的。如果使用“方法區”這個術語,在討論概念中的JVM時就安全得多——大家都必須實現出這個表象。
當然如何實現又是另一回事了。Oracle JRockit沒有PermGen,IBM J9也沒有,事實上有這麼一塊空間特別管理的反而是少數吧orz

===============================================================

費那麼多口舌,最後點題:請別再拿“String s = new String("xyz");創建了多少個String實例”來面試了吧,既沒意義又不漲面子。

困,睡覺去……
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章