<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java中final和static修飾的變量是在什么時(shí)候賦值的?

          共 4189字,需瀏覽 9分鐘

           ·

          2021-01-18 11:41

          開(kāi)始

          一位朋友在群里問(wèn)了這樣一個(gè)問(wèn)題:

          本著樂(lè)于助人的想法,我當(dāng)時(shí)給出的回答:


          后來(lái)我總覺(jué)得哪里不對(duì)勁。

          于是我仔細(xì)翻閱了《Java虛擬機(jī)規(guī)范》和《深入理解Java虛擬機(jī)》關(guān)于這一部分的內(nèi)容。

          害!發(fā)現(xiàn)自己理解的有問(wèn)題。

          因?yàn)樽约旱睦斫獬鲥e(cuò)而誤導(dǎo)了別人,實(shí)在是讓我萬(wàn)分羞愧!

          于是我加了這位朋友的好友,向這位朋友表達(dá)了歉意。

          這位朋友也非常隨和,對(duì)此表示理解。

          今天討論的問(wèn)題就是從這個(gè)故事開(kāi)始的。


          final修飾的實(shí)例變量



          我們先分析一下這個(gè)問(wèn)題:

          深入Java虛擬機(jī)有一句是ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值,只有被static關(guān)鍵字修飾的變量才可以使用這項(xiàng)屬性。但為什么private final a = 10也可以被賦值

          我翻閱了《深入理解Java虛擬機(jī)》第二版,在第191頁(yè),確實(shí)有前面那句話


          書(shū)中說(shuō)的很清楚,ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值。

          那就意味著只有static修飾的類變量才會(huì)在class文件中對(duì)應(yīng)的字段表加上ConstantValue屬性嗎?

          答案是否定的。

          用final修飾的實(shí)例變量,編譯成class文件的時(shí)候,對(duì)應(yīng)的字段表也有可能會(huì)加上ConstantValue屬性。

          注意,我這里用了“可能”這兩個(gè)字,因?yàn)檫@是有條件的。

          哪些情況會(huì)有ConstantValue屬性呢?

          我們寫(xiě)一段代碼,列舉一下用final修飾的實(shí)例變量的幾種情況。

          然后用javap -verbose命令反編譯查看字節(jié)碼:

          我們可以看到,在字段表集合里面有四個(gè)字段表:

          分表對(duì)應(yīng)著a,b,c,d,e五個(gè)實(shí)例屬性。

          他們都帶有ACC_PUBLIC(public)ACC_FINAL(final)的訪問(wèn)標(biāo)志。

          但只有a和b對(duì)應(yīng)的字段表帶有ConstantValue屬性。

          不難得出:

          用final修飾不是在構(gòu)造方法賦值的String類型或者基本類型成員變量,編譯成字節(jié)碼文件時(shí),對(duì)應(yīng)的字段表也會(huì)帶有ConstantValue屬性。

          這個(gè)結(jié)論不和《深入理解Java虛擬機(jī)》沖突嗎?

          于是我翻閱了官網(wǎng)的JVM規(guī)范,在4.7.2部分我找到了這樣一句話:

          書(shū)中說(shuō)的很清楚:

          如果field_info(字段表)表示的非靜態(tài)字段包含了ConstantValue屬性,那么這個(gè)ConstantValue屬性會(huì)被JVM虛擬機(jī)所忽略。

          也就是說(shuō),對(duì)于非靜態(tài)字段,就算你編譯器加上了ConstantValue屬性,JVM也會(huì)忽略掉,你加不加結(jié)果是一樣的。


          看完JVM規(guī)范里面的說(shuō)明,再回來(lái)看《深入理解Java虛擬機(jī)》里面的這句話:

          ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值,只有被static關(guān)鍵字修飾的類變量才可以使用這項(xiàng)屬性。

          作者的這句話的前半句沒(méi)有什么爭(zhēng)議,但我覺(jué)得后半句的表述的不太明確,容易造成誤解。

          以我的理解,應(yīng)該是“只有被static關(guān)鍵字修飾的類變量才可以使用ConstantValue這項(xiàng)屬性來(lái)進(jìn)行初始化,否則使用這項(xiàng)屬性也會(huì)被JVM忽略掉

          好了,我們?cè)倩氐侥俏慌笥褑?wèn)的問(wèn)題:

          為什么private final a = 10也可以被賦值?

          首先,這個(gè)問(wèn)題的本身就問(wèn)的不太準(zhǔn)確。

          我理解這位朋友真正想問(wèn)的是“為什么private final a = 10也可以通過(guò)ConstantValue屬性的形式賦值?”

          我覺(jué)得這是一個(gè)很好的問(wèn)題。

          這位朋友通過(guò)實(shí)驗(yàn)發(fā)現(xiàn)用final修飾的實(shí)例變量對(duì)應(yīng)的字段表有ConstantValue屬性。

          結(jié)合《深入理解Java虛擬機(jī)》,他認(rèn)為a是通過(guò)ConstantValue屬性讓虛擬機(jī)知道然后為其賦值的。

          最后他發(fā)現(xiàn)和書(shū)中沖突,于是提出了上文的這個(gè)問(wèn)題。

          這位朋友的思路有問(wèn)題嗎?我覺(jué)得是沒(méi)有問(wèn)題的。

          不過(guò)這樣的理解是對(duì)的嗎?顯然是不對(duì)的。

          因?yàn)樘摂M機(jī)規(guī)范是這樣規(guī)范的。對(duì)于非靜態(tài)字段,ConstantValue屬性是不會(huì)生效的。

          至于為什么要這樣設(shè)計(jì),功力不夠的我暫時(shí)無(wú)法理解設(shè)計(jì)者的想法。


          那單獨(dú)用final修飾的實(shí)例變量到底是在什么時(shí)候賦值的呢?

          這個(gè)問(wèn)題也不難回答,看一下字節(jié)碼就清楚了。

          圖片可點(diǎn)擊放大

          通過(guò)查看字節(jié)碼,我們可以看到生成了一個(gè)方法,右邊是它的字節(jié)碼指令。

          什么是方法?我們看看JVM規(guī)范上的解釋:

          我們溫習(xí)一下這個(gè)英語(yǔ)四級(jí)短語(yǔ):appear as

          然后,我們一起翻譯一下:

          在JVM層面上,每一個(gè)用Java寫(xiě)的構(gòu)造方法都表現(xiàn)為實(shí)例初始方法,這個(gè)方法就是方法。記住,這個(gè)方法會(huì)在實(shí)例初始化的時(shí)候被調(diào)用。

          我們?cè)賮?lái)看一下putfield這個(gè)字節(jié)碼指令的含義:

          為指定的類的實(shí)例域賦值的,也就是為實(shí)例變量賦值的指令。

          知道了方法是什么和putfield的含義后,結(jié)合上面的字節(jié)碼,不難得出:

          這些用final修飾實(shí)例變量是在實(shí)例構(gòu)造器方法里面賦值的,也就是對(duì)象創(chuàng)建的時(shí)候賦值。


          static修飾的類變量



          上面講到ConstantValue屬性的作用是通知虛擬機(jī)為靜態(tài)變量賦值。

          什么是靜態(tài)變量?static修飾的變量

          那static修飾的變量是什么時(shí)候加載的呢?

          在這之前,我需要給你把類加載的幾個(gè)過(guò)程大致講一下:

          類的生命周期由7個(gè)階段組成,類加載說(shuō)的是前5個(gè)階段:

          即加載—>驗(yàn)證—>準(zhǔn)備—>解析—>初始化。

          的生命周期圖

          我們簡(jiǎn)單過(guò)一下這幾個(gè)階段:

          • 加載:將字節(jié)碼所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。

          • 驗(yàn)證:驗(yàn)證字節(jié)碼格式,確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。

          • 準(zhǔn)備:創(chuàng)建類或者接口的靜態(tài)字段,并為靜態(tài)變量設(shè)置初始值。

          • 解析:將常量池內(nèi)的符號(hào)引用替換為直接引用。

          • 初始化:執(zhí)行類構(gòu)造器方法。


          上面出現(xiàn)類構(gòu)造器方法,這個(gè)方法又是個(gè)什么東西呢?

          JVM 規(guī)范這樣寫(xiě)道:

          說(shuō)白了,編譯器會(huì)收集所有靜態(tài)變量的賦值動(dòng)作、所有靜態(tài)代碼塊,合并產(chǎn)生一個(gè)方法,即方法。

          方法在上面那張類加載圖中的的初始化階段執(zhí)行。

          現(xiàn)在你應(yīng)該對(duì)類加載過(guò)程有一個(gè)大致的了解了。

          回到static修飾的變量(類變量),類變量有兩種賦值方式可以選擇:

          • 使用ConstantValue屬性賦值。


          • 在類構(gòu)造器方法中賦值。


          目前Oracle公司實(shí)現(xiàn)的Javac編譯器的選擇是:

          • final+static修飾:使用ConstantValue屬性賦值。


          • 僅使用static修飾:在方法中賦值。這個(gè)方法在類加載的初始化階段執(zhí)行

          需要注意點(diǎn)的是,用生成ConstantValue屬性來(lái)進(jìn)行初始化,這個(gè)變量必須是基本類型或者java.lang.String類型。

          對(duì)于這一點(diǎn),我們也可以通過(guò)javap -verbose命令反編譯驗(yàn)證一下:

          為什么呢?

          這是因?yàn)镃lass文件格式的常量類型中只有與基本屬性和字符串相對(duì)應(yīng)的字面量,所以就算ConstantValue屬性想支持別的類型也無(wú)能為力。


          final+static修飾的常量



          上面我們說(shuō)過(guò),方法是在類加載的初始化階段賦值的。

          那static+final修飾的常量是在類加載的那一階段進(jìn)行的呢?

          我們可以看一下JVM規(guī)范:

          我們可以看到在JVM規(guī)范里面,static+final修飾的常量是在初始化階段執(zhí)行方法之前執(zhí)行的。


          咦?網(wǎng)上的博客不都是在類加載的準(zhǔn)備階段會(huì)對(duì)普通類屬性賦初始值,對(duì)帶有ConstantValue的類屬性直接賦值嗎?

          《深入理解Java虛擬機(jī)》也是這樣說(shuō)的啊?

          書(shū)上是錯(cuò)的嗎?不是的,因?yàn)椤渡钊肜斫釰ava虛擬機(jī)》里面講的具體實(shí)現(xiàn),是基于HotSpot VM講的。

          確確實(shí)實(shí),HotSpot VM就是這么干的,我們也可以在openJdk中找到對(duì)應(yīng)的源碼:

          跟蹤源碼可以發(fā)現(xiàn)HotSpot VM對(duì)基本類型或者字符串類型的常量的賦值確實(shí)在準(zhǔn)備階段完成了。

          但一個(gè)很關(guān)鍵的點(diǎn)是,雖然沒(méi)在初始化階段賦值,仍然在調(diào)用方法之前賦值了。

          外界是不會(huì)觀察到HotSpot VM提前做了這個(gè)初始化賦值的,所以是沒(méi)問(wèn)題的。

          不過(guò)要記住的是:

          JVM規(guī)范里明確說(shuō)了正確的初始化時(shí)機(jī)是在“初始化(Initialization)”階段。


          總結(jié)



          1. 單獨(dú)用final修飾的變量也有可能在字節(jié)碼找到對(duì)應(yīng)的ConstantValue屬性,但是會(huì)被JVM忽略掉。

          2. final修飾的實(shí)例屬性,在實(shí)例創(chuàng)建的時(shí)候才會(huì)賦值。

          3. static修飾的類屬性,在類加載的準(zhǔn)備階段賦初值,初始化階段賦值。

          4. static+final修飾的String類型或者基本類型常量,JVM規(guī)范建議在初始化階段賦值,但是HotSpot VM直接在準(zhǔn)備階段就賦值了。

          5. static+final修飾的其他引用類型常量,賦值步驟和第二點(diǎn)的流程是一樣的。

          還有一點(diǎn),一定不要把《深入理解Java虛擬機(jī)》和《Java虛擬機(jī)規(guī)范》搞混了。

          • 《Java虛擬機(jī)規(guī)范》是官方JVM規(guī)范文檔翻譯而來(lái)的,所有的JVM實(shí)現(xiàn)都要遵從規(guī)范,但有強(qiáng)制要求的規(guī)范和建議的規(guī)范。

          • 《深入理解Java虛擬機(jī)》是作者根據(jù)自己的理解,結(jié)合HotSpot VM的具體實(shí)現(xiàn),為了讓讀者更容易理解JVM而寫(xiě)的一本書(shū)。

          寫(xiě)在最后



          本人才疏學(xué)淺,OpenJdk源碼也理解的不夠透徹。

          為了說(shuō)清楚文中的一些知識(shí),我翻閱了很多資料。

          但是還是有一部分的知識(shí)點(diǎn)無(wú)法找到權(quán)威的資料證明。

          不過(guò)我盡量都基于官方文檔展開(kāi)分析,如果有認(rèn)識(shí)有差錯(cuò)的地方,歡迎指出!我定會(huì)在第一時(shí)間修改,不誤導(dǎo)別人!

          最后,感謝你的閱讀!

          如果可以,點(diǎn)贊再看關(guān)注,謝謝你!


          —————END—————


          推薦閱讀:



          最近面試BAT,整理一份面試資料Java面試BAT通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          明天見(jiàn)(??ω??)?
          瀏覽 81
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久久国产精品 | 成人午夜av | 亚洲一区色 | 黄色电影中文字幕 | 欧洲成人黄色 |