日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

String s = new String(xyz)创建了几个实例你真的能答对吗?

發(fā)布時(shí)間:2025/3/16 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 String s = new String(xyz)创建了几个实例你真的能答对吗? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

String s = new String("xyz"); 創(chuàng)建了幾個(gè)實(shí)例?

這是一道很經(jīng)典的面試題,在一本所謂的Java寶典上,我看到的“標(biāo)準(zhǔn)答案”是這樣的:

兩個(gè),一個(gè)堆區(qū)的“xyz”,一個(gè)棧區(qū)指向“xyz”的s。

這個(gè)所謂的“標(biāo)準(zhǔn)答案”槽點(diǎn)更多,后面我們會(huì)慢慢分析。

雖然答案很離譜,但是我覺(jué)得這個(gè)問(wèn)題本身也不具有什么意義,因?yàn)閱?wèn)題沒(méi)有既定義“創(chuàng)建”的具體含義,又沒(méi)有指定“創(chuàng)建”的時(shí)間,是運(yùn)行時(shí)嗎?包不包括類(lèi)加載的時(shí)候?有沒(méi)有上下文代碼語(yǔ)境?也沒(méi)有定義實(shí)例是指什么實(shí)例,是指Java實(shí)例嗎?還是單指String實(shí)例?包不包括JVM中的C++實(shí)例?

顯然,這個(gè)問(wèn)題是一個(gè)“有問(wèn)題的問(wèn)題”。這個(gè)答案也是一個(gè)“有問(wèn)題的答案”。

String結(jié)構(gòu)

在分析之前,為了能更好的理解后面的知識(shí)點(diǎn),我們需要對(duì)Java中的String結(jié)構(gòu)有一個(gè)大致了解:

從上圖可以看出,String類(lèi)有三個(gè)屬性:

  • value:char數(shù)組,用于用于存儲(chǔ)字符

  • hash:緩存字符串的哈希碼,默認(rèn)為0

  • serialVersionUID:序列化用的

正常的問(wèn)題與“合理的解釋”

在上面的題干上加上"String"限定詞,可以得到一個(gè)比較合理的問(wèn)題:

String s = new String("xyz");創(chuàng)建幾個(gè)String實(shí)例?

對(duì)于這個(gè)問(wèn)題,網(wǎng)上也有很多錯(cuò)誤的答案和解析,我認(rèn)為這個(gè)答案看起來(lái)比較合理:

兩個(gè),一個(gè)是字符串字面量"xyz"所對(duì)應(yīng)的、存在于全局共享的常量池中的實(shí)例,另一個(gè)是通過(guò)new關(guān)鍵字創(chuàng)建并初始化的、內(nèi)容(字符)與"xyz"相同的實(shí)例。如果常量池中如果已經(jīng)存在這個(gè)字符串,就只會(huì)創(chuàng)建一個(gè)。同時(shí)在棧區(qū)還會(huì)有一個(gè)對(duì)new出來(lái)的String實(shí)例的引用s。

考慮到了棧與堆,提到了常量池,我認(rèn)為這已經(jīng)達(dá)到大部分面試官對(duì)這個(gè)題目答案的期許了,或許這也是面試官想要考察的點(diǎn)。

但這個(gè)答案也僅是比較合理,并不完全正確。

首先,我不理解的是為什么很多答主總是用“常量池”來(lái)代替“字符串常量池”,在Java體系中,其實(shí)是有三個(gè)常量池的,三個(gè)常量池的概念和用處都不相同,混淆在一起容易給別人造成誤解。

其次,就算答主說(shuō)的“常量池”就是“字符串常量池”,可“字符串常量池”中存的是String實(shí)例的引用,而不是字符串,這是有很大區(qū)別的。而且這個(gè)答案是沒(méi)有考慮代碼執(zhí)行的環(huán)境。

這些問(wèn)題,下面都會(huì)一一分析。

分清變量和實(shí)例

我們先回到開(kāi)頭的問(wèn)題與“標(biāo)準(zhǔn)答案” :

String s = new String("xyz"); 創(chuàng)建了幾個(gè)實(shí)例?

兩個(gè),一個(gè)堆區(qū)的“xyz”,一個(gè)棧區(qū)指向“xyz”的s

很明顯寫(xiě)答案的人沒(méi)有把變量和實(shí)例分清楚。在Java里,變量就是變量,類(lèi)型的變量只是對(duì)某個(gè)對(duì)象實(shí)例或者null的,不是實(shí)例本身。聲明變量的個(gè)數(shù)跟創(chuàng)建實(shí)例的個(gè)數(shù)沒(méi)有必然關(guān)系。

舉個(gè)例子:

String?s1?=?"xyz";?? String?s2?=?s1.concat("");?? String?s3?=?null;?? new?String(s1);??

這段代碼會(huì)涉及3個(gè)String類(lèi)型的變量:?

  • s1,指向下面String實(shí)例的1?

  • s2,指向與s1相同?

  • s3,值為null,不指向任何實(shí)例?

以及3個(gè)String實(shí)例:

  • "xyz"字面量對(duì)應(yīng)的駐留的字符串常量的String實(shí)例?

  • ""空字符串字面量對(duì)應(yīng)的駐留的字符串常量的String實(shí)例?

  • 通過(guò)new String(String)創(chuàng)建的新String實(shí)例,沒(méi)有任何變量指向它

類(lèi)加載

對(duì)于String s = new String("xyz");創(chuàng)建幾個(gè)String實(shí)例?這個(gè)問(wèn)題。

似乎網(wǎng)上的所有答案都把類(lèi)加載過(guò)程和實(shí)際執(zhí)行過(guò)程合在一起分析的??雌饋?lái)是沒(méi)有什么問(wèn)題的,因?yàn)橄胍獔?zhí)行某個(gè)代碼片段,其所在的類(lèi)必然要被加載,而且對(duì)于同一個(gè)類(lèi)加載器,最多加載一次。

但是我們看一下這段代碼的字節(jié)碼:

字節(jié)碼中似乎只出現(xiàn)了一次new java/lang/String,也就是只創(chuàng)建了一個(gè)String實(shí)例。也就是說(shuō)原問(wèn)題中的代碼在每執(zhí)行一次只會(huì)新創(chuàng)建一個(gè)String實(shí)例。

這里的ldc指令只是把先前在類(lèi)加載過(guò)程中已經(jīng)創(chuàng)建好的一個(gè)String實(shí)例("xyz")的一個(gè)引用壓到操作數(shù)棧頂而已,并沒(méi)有創(chuàng)建新的String實(shí)例。

不是應(yīng)該有兩個(gè)實(shí)例嗎?還有一個(gè)String實(shí)例是在什么時(shí)候創(chuàng)建的呢?

還有一個(gè)String實(shí)例在類(lèi)加載的時(shí)候創(chuàng)建。

我們都知道類(lèi)加載的解析階段是Java虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,根據(jù)JVM規(guī)范,符合規(guī)范的JVM實(shí)現(xiàn)應(yīng)該在類(lèi)加載的過(guò)程中創(chuàng)建并駐留一個(gè)String實(shí)例作為常量來(lái)對(duì)應(yīng)"xyz"字面量,具體是在類(lèi)加載的解析階段進(jìn)行的。這個(gè)常量是全局共享的,只在先前尚未有內(nèi)容相同的字符串駐留過(guò)的前提下才需要?jiǎng)?chuàng)建新的String實(shí)例。?

所以你可以理解成:

在類(lèi)加載的解析階段,其實(shí)已經(jīng)創(chuàng)建了一個(gè)String實(shí)例,執(zhí)行代碼的時(shí)候,又new了一個(gè)String實(shí)例。

JVM的優(yōu)化

以上討論都只是針對(duì)規(guī)范所定義的Java語(yǔ)言與Java虛擬機(jī)而言。概念上是如此,但實(shí)際的JVM實(shí)現(xiàn)可以做得更優(yōu)化,原問(wèn)題中的代碼片段有可能在實(shí)際執(zhí)行的時(shí)候一個(gè)String實(shí)例也不會(huì)完整創(chuàng)建(沒(méi)有分配空間)。?

不結(jié)合上下文代碼來(lái)看就直接說(shuō)是“標(biāo)準(zhǔn)答案”就是耍流氓。

我們看下這段代碼:

運(yùn)行這段代碼,會(huì)不斷的創(chuàng)建String對(duì)象吃?xún)?nèi)存,然后頻繁的造成GC。對(duì)于這個(gè)結(jié)論相信大家都沒(méi)有意見(jiàn)。

我們加上-XX:+PrintGC打印日志;加上-XX:-DoEscapeAnalysis關(guān)閉逃逸分析(JDK8默認(rèn)開(kāi)啟此優(yōu)化,我們先關(guān)閉)

運(yùn)行一下看看:

結(jié)果確實(shí)如我們所料,不斷的創(chuàng)建String對(duì)象吃?xún)?nèi)存,造成頻繁GC。

我們現(xiàn)在將-XX:-DoEscapeAnalysis改成-XX:+DoEscapeAnalysis,重新跑一下這段代碼:

神奇的事情發(fā)生了,繼續(xù)跑下去也沒(méi)有再打出GC日志了。難道新創(chuàng)建String對(duì)象都不吃?xún)?nèi)存了么??

實(shí)際情況是:經(jīng)過(guò)HotSpot VM的的優(yōu)化后,newString()方法不會(huì)新創(chuàng)建String實(shí)例了。這樣自然不吃?xún)?nèi)存,也就不再觸發(fā)GC了。?

現(xiàn)在再來(lái)看開(kāi)篇的那個(gè)問(wèn)題,不結(jié)合具體情況,還能簡(jiǎn)單的說(shuō)String s = new String("xyz");會(huì)創(chuàng)建兩個(gè)String實(shí)例嗎?

我只是舉了一個(gè)逃逸分析的例子,HotSpot VM還有很多像這樣的優(yōu)化,比如方法內(nèi)聯(lián)、標(biāo)量替換和無(wú)用代碼削除。這些都會(huì)影響對(duì)象的創(chuàng)建的。

klass-oop

如果題干上沒(méi)有加上“Java”實(shí)例的定語(yǔ),那JVM中的oop實(shí)例我們也不應(yīng)該忽略。

為了后面能更好的說(shuō)清楚這一點(diǎn),先大致的介紹一下klass-opp模型。先做一個(gè)約定,全文只要涉及JVM具體實(shí)現(xiàn)的內(nèi)容都是基于Jdk8中HotSpot VM展開(kāi)的。

HotSpot VM是基于C++實(shí)現(xiàn),而C++是一門(mén)面向?qū)ο蟮恼Z(yǔ)言,本身是具備面向?qū)ο蠡咎卣鞯?#xff0c;所以Java中的對(duì)象表示,最簡(jiǎn)單的做法是為每個(gè)Java類(lèi)生成一個(gè)C++類(lèi)與之對(duì)應(yīng)。但HotSpot VM并沒(méi)有這么做,而是設(shè)計(jì)了一套klass-oop模型。

klass,它是Java類(lèi)的元信息在JVM中的存在形式。一個(gè)Java類(lèi)被JVM類(lèi)加載器加載之后,就是以klass的形式存在于JVM之中。

oop,它是Java對(duì)象在JVM中的存在形式。每創(chuàng)建一個(gè)新的對(duì)象,在JVM內(nèi)部就會(huì)相應(yīng)地創(chuàng)建一個(gè)對(duì)應(yīng)類(lèi)型的OOP對(duì)象。

其中instanceOopDesc表示非數(shù)組對(duì)象;

arrayOopDesc表示數(shù)組對(duì)象;

而objArrayOopDesc表示引用類(lèi)型數(shù)組對(duì)象;

typeArrayOopDesc表示基本類(lèi)型數(shù)組對(duì)象。

舉個(gè)例子:Java中String類(lèi)的一個(gè)實(shí)例,在JVM中會(huì)有一個(gè)對(duì)應(yīng)的instanceOopDesc實(shí)例。

字符串常量池

在Java體系中,有三種常量池:

  • class字節(jié)碼中的常量池:存在于硬盤(pán)上。主要存放字面量和符號(hào)引用。

  • 運(yùn)行時(shí)常量池:方法區(qū)的一部分。我們常說(shuō)的常量池,就是指這一塊區(qū)域。

  • 字符串常量池:存在于堆區(qū)。這個(gè)常量池在JVM層面就是一個(gè)StringTable,只存儲(chǔ)對(duì)java.lang.String實(shí)例的引用,而不存儲(chǔ)String對(duì)象的內(nèi)容。一般我們說(shuō)一個(gè)字符串進(jìn)入了字符串常量池其實(shí)是說(shuō)在這個(gè)StringTable中保存了對(duì)它的引用,反之,如果說(shuō)沒(méi)有在其中就是說(shuō)StringTable中沒(méi)有對(duì)它的引用。

今天,我們要了解的是字符串常量池。

字符串常量池,即String Pool。在JVM中對(duì)應(yīng)的類(lèi)是StringTable,底層實(shí)現(xiàn)是一個(gè)Hashtable。利用的是哈希思想。

看一段往字符串常量池添加字符串引用的方法:

上面面這段代碼雖然是C++寫(xiě)的,但我相信學(xué)過(guò)Java的人都能看懂,至少也能明白這段代碼干了什么事情。無(wú)非是通過(guò)String的內(nèi)容+長(zhǎng)度生成的hashValue值定位下標(biāo)index,然后將Java的String類(lèi)的實(shí)例對(duì)應(yīng)的instanceOopDesc封裝成HashtableEntry作為存儲(chǔ)結(jié)構(gòu)存儲(chǔ)到常量池。

補(bǔ)充完字符串常量池的知識(shí)之后,我們?cè)倩氐轿恼麻_(kāi)頭的那一題:

String s = new String("xyz");創(chuàng)建了幾個(gè)實(shí)例?

如果包括JVM中的C++實(shí)例的話,有兩個(gè)Java的String實(shí)例,兩個(gè)String實(shí)例對(duì)應(yīng)的instanceOopDesc實(shí)例,還有一個(gè)char[]數(shù)組對(duì)應(yīng)的typeArrayOopDesc實(shí)例,加一起一共是5個(gè)。也可以說(shuō)2個(gè)String實(shí)例加上3個(gè)oop實(shí)例。

不理解的可以看下面這張內(nèi)存圖(圖中省略了兩個(gè)String對(duì)應(yīng)的instanceOopDesc實(shí)例)。

總結(jié)

String s = new String("xyz"); 創(chuàng)建了幾個(gè)實(shí)例?

通過(guò)以上的分析,我們會(huì)發(fā)現(xiàn),每在這道題目的題干上每加一個(gè)定語(yǔ),這道題目就會(huì)有不同的答案。是否考慮類(lèi)加載過(guò)程,是否考慮JVM優(yōu)化,是否包括對(duì)應(yīng)的oop實(shí)例等等等等,都會(huì)有不同的答案。

如果下次有人問(wèn)你這個(gè)問(wèn)題,不妨把這篇的文章分享給他。

寫(xiě)在最后

為了寫(xiě)這一篇文章,我翻看了很多@RednaxelaFX前輩和周志明前輩的博客,過(guò)程中收益良多。在這里感謝前輩們?yōu)閲?guó)內(nèi)JVM的科普與發(fā)展做出的貢獻(xiàn)!

文中涉及代碼:https://github.com/xiaoyingzhi/blog

參考文章:

http://isfeasible.cn/posts/view/5b84b6ab3957bb300a5bca94

https://www.iteye.com/blog/rednaxelafx-774673

http://lovestblog.cn/blog/2014/06/28/hsdb-string/

有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

歡迎大家關(guān)注Java之道公眾號(hào)

好文章,我在看??

總結(jié)

以上是生活随笔為你收集整理的String s = new String(xyz)创建了几个实例你真的能答对吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。