国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

[轉(zhuǎn)] 請別再拿“String s = new String("xyz");創(chuàng)建了多少個String實(shí)例”來面試了吧

2019-11-14 23:04:56
字體:
供稿:網(wǎng)友
[轉(zhuǎn)] 請別再拿“String s = new String("xyz");創(chuàng)建了多少個String實(shí)例”來面試了吧

這帖是用來回復(fù)高級語言虛擬機(jī)圈子里的一個問題,一道java筆試題的。本來因?yàn)橐姷锰嘁呀?jīng)吐槽無力,但這次實(shí)在忍不住了就又爆發(fā)了一把。寫得太長干脆單獨(dú)開了一帖。順帶廣告:對JVM感興趣的同學(xué)們同志們請多多支持高級語言虛擬機(jī)圈子以下是回復(fù)內(nèi)容。文中的“樓主”是針對原問題帖而言。===============================================================樓主是看各種寶典了么……以后我面試人的時候就要專找寶典答案是錯的來問,方便篩人orz樓主要注意了:這題或類似的題雖然經(jīng)常見,但使用這個描述方式實(shí)際上沒有任何意義:

引用問題:Java代碼收藏代碼
  1. Strings=newString("xyz");
創(chuàng)建了幾個String Object?

這個問題自身就沒有合理的答案,樓主所引用的“標(biāo)準(zhǔn)答案”自然也就不準(zhǔn)確了:

引用答案:兩個(一個是“xyz”,一個是指向“xyz”的引用對象s)

(好吧這個答案的吐槽點(diǎn)很多……大家慢慢來)這問題的毛病是什么呢?它并沒有定義“創(chuàng)建了”的意義。什么叫“創(chuàng)建了”?什么時候創(chuàng)建了什么?而且這段Java代碼片段實(shí)際運(yùn)行的時候真的會“創(chuàng)建兩個String實(shí)例”么?如果這道是面試題,那么可以當(dāng)面讓面試官澄清“創(chuàng)建了”的定義,然后再對應(yīng)的回答。這種時候面試官多半會讓被面試者自己解釋,那就好辦了,好好曬給面試官看。如果是筆試題就沒有提問要求澄清的機(jī)會了。不過會出這種題目的地方水平多半也不怎么樣。說不定出題的人就是從各種寶典上把題抄來的,那就按照寶典把那不大對勁的答案寫上去就能混過去了===============================================================先換成另一個問題來問:

引用問題:Java代碼收藏代碼
  1. Strings=newString("xyz");
在運(yùn)行時涉及幾個String實(shí)例?

一種合理的解答是:

引用答案:兩個,一個是字符串字面量"xyz"所對應(yīng)的、駐留(intern)在一個全局共享的字符串常量池中的實(shí)例,另一個是通過new String(String)創(chuàng)建并初始化的、內(nèi)容與"xyz"相同的實(shí)例

這是根據(jù)Java語言規(guī)范相關(guān)規(guī)定可以給出的合理答案。考慮到Java語言規(guī)范中明確指出了:

The Java Language Specification, Third Edition 寫道The Java PRogramming language is normally compiled to the bytecoded instruction set and binary format defined inThe Java Virtual Machine Specification, Second Edition(Addison-Wesley, 1999).

也就是規(guī)定了Java語言一般是編譯為Java虛擬機(jī)規(guī)范所定義的Class文件,但并沒有規(guī)定“一定”(must),留有不使用JVM來實(shí)現(xiàn)Java語言的余地。考慮上Java虛擬機(jī)規(guī)范,確實(shí)在這段代碼里涉及的常量種類為CONSTANT_String_info的字符串常量也只有"xyz"一個。CONSTANT_String_info是用來表示Java語言中String類型的常量表達(dá)式的值(包括字符串字面量)用的常量種類,只在這個層面上考慮的話,這個解答也沒問題。所以這種解答可以認(rèn)為是合理的。值得注意的是問題中“在運(yùn)行時”既包括類加載階段,也包括這個代碼片段自身執(zhí)行的時候。下文會再討論這個細(xì)節(jié)與樓主原本給出的問題的關(guān)系。碰到這種問題首先應(yīng)該想到去查閱相關(guān)的規(guī)范,這里具體是Java語言規(guī)范與Java虛擬機(jī)規(guī)范,以及一些相關(guān)API的JavaDoc。很多人喜歡把“按道理說”當(dāng)作口頭禪,規(guī)范就是用來定義各種“道理”的——“為什么XXX是YYY的意思?”“因?yàn)橐?guī)范里是這樣定義的!”——無敵了。在Java虛擬機(jī)規(guī)范中相關(guān)的定義有下面一些:

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看作參考實(shí)現(xiàn)(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. Strings=newString("xyz");
涉及用戶聲明的幾個String類型的變量?

答案也很簡單:

引用答案:一個,就是String s。

把問題換成下面這個版本,答案也一樣:

引用問題:Java代碼收藏代碼
  1. Strings=null;
涉及用戶聲明的幾個String類型的變量?

Java里變量就是變量,引用類型的變量只是對某個對象實(shí)例或者null的引用,不是實(shí)例本身。聲明變量的個數(shù)跟創(chuàng)建實(shí)例的個數(shù)沒有必然關(guān)系,像是說:

Java代碼收藏代碼
  1. Strings1="a";
  2. Strings2=s1.concat("");
  3. Strings3=null;
  4. newString(s1);

這段代碼會涉及3個String類型的變量,1、s1,指向下面String實(shí)例的12、s2,指向與s1相同3、s3,值為null,不指向任何實(shí)例以及3個String實(shí)例,1、"a"字面量對應(yīng)的駐留的字符串常量的String實(shí)例2、""字面量對應(yīng)的駐留的字符串常量的String實(shí)例(String.concat()是個有趣的方法,當(dāng)發(fā)現(xiàn)傳入的參數(shù)是空字符串時會返回this,所以這里不會額外創(chuàng)建新的String實(shí)例)3、通過new String(String)創(chuàng)建的新String實(shí)例;沒有任何變量指向它。===============================================================回到樓主開頭引用的問題與“標(biāo)準(zhǔn)答案”

引用問題:Java代碼收藏代碼
  1. Strings=newString("xyz");
創(chuàng)建了幾個String Object? 答案:兩個(一個是“xyz”,一個是指向“xyz”的引用對象s)

用歸謬法論證。假定問題問的是“在執(zhí)行這段代碼片段時創(chuàng)建了幾個String實(shí)例”。如果“標(biāo)準(zhǔn)答案”是正確的,那么下面的代碼片段在執(zhí)行時就應(yīng)該創(chuàng)建4個String實(shí)例了:

Java代碼收藏代碼
  1. Strings1=newString("xyz");
  2. Strings2=newString("xyz");

馬上就會有人跳出來說上下兩個"xyz"字面量都是引用了同一個String對象,所以不應(yīng)該是創(chuàng)建了4個對象。那么應(yīng)該是多少個?運(yùn)行時的類加載過程與實(shí)際執(zhí)行某個代碼片段,兩者必須分開討論才有那么點(diǎn)意義。為了執(zhí)行問題中的代碼片段,其所在的類必然要先被加載,而且同一個類最多只會被加載一次(要注意對JVM來說“同一個類”并不是類的全限定名相同就足夠了,而是<類全限定名, 定義類加載器>一對都相同才行)。根據(jù)上文引用的規(guī)范的內(nèi)容,符合規(guī)范的JVM實(shí)現(xiàn)應(yīng)該在類加載的過程中創(chuàng)建并駐留一個String實(shí)例作為常量來對應(yīng)"xyz"字面量;具體是在類加載的resolve階段進(jìn)行的。這個常量是全局共享的,只在先前尚未有內(nèi)容相同的字符串駐留過的前提下才需要創(chuàng)建新的String實(shí)例。等到真正執(zhí)行原問題中的代碼片段時,JVM需要執(zhí)行的字節(jié)碼類似這樣:

Java bytecode代碼收藏代碼
  1. 0:new#2;//classjava/lang/String
  2. 3:dup
  3. 4:ldc#3;//Stringxyz
  4. 6:invokespecial#4;//Methodjava/lang/String."<init>":(Ljava/lang/String;)V
  5. 9:astore_1

這之中出現(xiàn)過多少次new java/lang/String就是創(chuàng)建了多少個String對象。也就是說原問題中的代碼在每執(zhí)行一次只會新創(chuàng)建一個String實(shí)例。這里,ldc指令只是把先前在類加載過程中已經(jīng)創(chuàng)建好的一個String對象("xyz")的一個引用壓到操作數(shù)棧頂而已,并不新創(chuàng)建String對象。所以剛才用于歸謬的代碼片段:

Java代碼收藏代碼
  1. Strings1=newString("xyz");
  2. Strings2=newString("xyz");

每執(zhí)行一次只會新創(chuàng)建2個String實(shí)例。---------------------------------------------------------------為了避免一些同學(xué)犯糊涂,再強(qiáng)調(diào)一次:在Java語言里,“new”表達(dá)式是負(fù)責(zé)創(chuàng)建實(shí)例的,其中會調(diào)用構(gòu)造器去對實(shí)例做初始化;構(gòu)造器自身的返回值類型是void,并不是“構(gòu)造器返回了新創(chuàng)建的對象的引用”,而是new表達(dá)式的值是新創(chuàng)建的對象的引用。對應(yīng)的,在JVM里,“new”字節(jié)碼指令只負(fù)責(zé)把實(shí)例創(chuàng)建出來(包括分配空間、設(shè)定類型、所有字段設(shè)置默認(rèn)值等工作),并且把指向新創(chuàng)建對象的引用壓到操作數(shù)棧頂。此時該引用還不能直接使用,處于未初始化狀態(tài)(uninitialized);如果某方法a含有代碼試圖通過未初始化狀態(tài)的引用來調(diào)用任何實(shí)例方法,那么方法a會通不過JVM的字節(jié)碼校驗(yàn),從而被JVM拒絕執(zhí)行。能對未初始化狀態(tài)的引用做的唯一一種事情就是通過它調(diào)用實(shí)例構(gòu)造器,在Class文件層面表現(xiàn)為特殊初始化方法“<init>”。實(shí)際調(diào)用的指令是invokespecial,而在實(shí)際調(diào)用前要把需要的參數(shù)按順序壓到操作數(shù)棧上。在上面的字節(jié)碼例子中,壓參數(shù)的指令包括dup和ldc兩條,分別把隱藏參數(shù)(新創(chuàng)建的實(shí)例的引用,對于實(shí)例構(gòu)造器來說就是“this”)與顯式聲明的第一個實(shí)際參數(shù)("xyz"常量的引用)壓到操作數(shù)棧上。在構(gòu)造器返回之后,新創(chuàng)建的實(shí)例的引用就可以正常使用了。關(guān)于構(gòu)造器的討論,可以參考我之前的一帖,實(shí)例構(gòu)造器是不是靜態(tài)方法?===============================================================以上討論都只是針對規(guī)范所定義的Java語言與Java虛擬機(jī)而言。概念上是如此,但實(shí)際的JVM實(shí)現(xiàn)可以做得更優(yōu)化,原問題中的代碼片段有可能在實(shí)際執(zhí)行的時候一個String實(shí)例也不會完整創(chuàng)建(沒有分配空間)。例如說,在x86、Windows Vista SP2、Sun JDK 6 update 14的fastdebug版上跑下面的測試代碼:

Java代碼收藏代碼
  1. publicclassC2EscapeAnalysisDemo{
  2. privatestaticvoidwarmUp(){
  3. IFoo[]array=newIFoo[]{
  4. newFooA(),newFooB(),newFooC(),newFooD()
  5. };
  6. for(inti=0;i<1000000;i++){
  7. array[i%array.length].foo();//megamorphiccallsitetopreventinlining
  8. }
  9. }
  10. publicstaticvoidmain(String[]args){
  11. while(true){
  12. warmUp();
  13. }
  14. }
  15. }
  16. interfaceIFoo{
  17. voidfoo();
  18. }
  19. classFooAimplementsIFoo{
  20. publicvoidfoo(){
  21. Strings1=newString("xyz");
  22. }
  23. }
  24. classFooBimplementsIFoo{
  25. publicvoidfoo(){
  26. Strings1=newString("xyz");
  27. Strings2=newString("xyz");
  28. }
  29. }
  30. classFooCimplementsIFoo{
  31. publicvoidfoo(){
  32. Strings1=newString("xyz");
  33. Strings2=newString("xyz");
  34. Strings3=newString("xyz");
  35. }
  36. }
  37. classFooDimplementsIFoo{
  38. publicvoidfoo(){
  39. Strings1=newString("xyz");
  40. Strings2=newString("xyz");
  41. Strings3=newString("xyz");
  42. Strings4=newString("xyz");
  43. }
  44. }

照常用javac用默認(rèn)參數(shù)編譯,然后先用server模式的默認(rèn)配置來跑,順帶打出GC和JIT編譯日志來看

Command prompt代碼收藏代碼
  1. java-server-verbose:gc-XX:+PrintCompilationC2EscapeAnalysisDemo

看到的日志的開頭一段如下:

Hotspot log代碼收藏代碼
  1. 1java.lang.String::charAt(33bytes)
  2. 2java.lang.Object::<init>(1bytes)
  3. 3java.lang.String::<init>(61bytes)
  4. 1%C2EscapeAnalysisDemo::warmUp@47(71bytes)
  5. 4FooA::foo(11bytes)
  6. 5FooB::foo(21bytes)
  7. 6FooC::foo(31bytes)
  8. 7FooD::foo(42bytes)
  9. [GC3072K->168K(32768K),0.0058325secs]
  10. [GC3240K->160K(32768K),0.0104623secs]
  11. [GC3232K->160K(32768K),0.0027323secs]
  12. [GC3232K->160K(35840K),0.0026220secs]
  13. [GC6304K->160K(35840K),0.0173733secs]
  14. [GC6304K->144K(41664K),0.0059720secs]
  15. [GC12432K->144K(41664K),0.0353320secs]
  16. [GC12432K->144K(54016K),0.0139333secs]
  17. 8C2EscapeAnalysisDemo::warmUp(71bytes)
  18. [GC24720K->160K(54016K),0.0697970secs]
  19. [GC24736K->160K(68800K),0.0261921secs]
  20. [GC39520K->160K(68800K),0.0958433secs]
  21. [GC39520K->160K(87
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 原平市| 乐安县| 福建省| 得荣县| 阳信县| 安图县| 邯郸县| 嘉兴市| 庆城县| 沅陵县| 岢岚县| 乐清市| 云南省| 桐梓县| 墨竹工卡县| 喀喇沁旗| 大悟县| 灵璧县| 旌德县| 龙胜| 哈巴河县| 澳门| 广丰县| 祁阳县| 文化| 石首市| 江达县| 迁安市| 婺源县| 龙口市| 潼南县| 秦安县| 井陉县| 土默特左旗| 中山市| 资兴市| 巍山| 岳阳县| 贡嘎县| 托里县| 砀山县|