從一道面試題深入了解java虛擬機(jī)內(nèi)存結(jié)構(gòu)
時(shí)間:2023-06-26 11:27:01 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-06-26 11:27:01 來(lái)源:網(wǎng)站運(yùn)營(yíng)
從一道面試題深入了解java虛擬機(jī)內(nèi)存結(jié)構(gòu):記得剛大學(xué)畢業(yè)時(shí),為了應(yīng)付面試,瘋狂的在網(wǎng)上刷JAVA的面試題,很多都靠死記硬背。其中有道面試題,給我的印象非常之深刻,有個(gè)大廠的面試官,順著這道題目,一直往下問(wèn),問(wèn)到j(luò)ava虛擬機(jī)的知識(shí),最后把我給問(wèn)住了。 我當(dāng)時(shí)的表情是這樣的:
后來(lái)我有機(jī)會(huì)面試別人了,也按照他的思路出面試題,很多已經(jīng)工作了2年的程序員,結(jié)果也和我當(dāng)年一樣,都敗在java虛擬機(jī)知識(shí)上。
我們先看面試題:
String str1 = "hello Alunbar";String str2 = new String(str1);
會(huì)創(chuàng)建幾個(gè)對(duì)象?
網(wǎng)上給出的解釋是創(chuàng)建2個(gè)對(duì)象,str1對(duì)象在常量池中,str2對(duì)象在堆中。
下面是我和面試官的對(duì)話。
面試官:上面的代碼創(chuàng)建了幾個(gè)對(duì)象?
我:2個(gè)。 面試官:為什么是2個(gè)呢?
我:str1對(duì)象在常量池中,str2對(duì)象在堆中。用“=”等號(hào)創(chuàng)建String對(duì)象時(shí),會(huì)先從字符串常量池中查找是否已經(jīng)存在字符串對(duì)象,存在就直接返回引用地址,否則創(chuàng)建字符串對(duì)象并返回引用地址。
面試官:為什么會(huì)在常量池中創(chuàng)建字符串對(duì)象?
我:。。。我思考了半分鐘,尷尬的回答不知道。
面試官:說(shuō)說(shuō)jvm虛擬機(jī)的內(nèi)存結(jié)構(gòu)。 我:。。。我再次面露難色,場(chǎng)面一度非常尷尬。
這次面試結(jié)束之后,我就回去瘋狂查找資料,了解jvm虛擬機(jī)的相關(guān)知識(shí)。
這也是我的第一次面試,給我的印象非常之深刻。
下面我們來(lái)說(shuō)說(shuō)面試官的兩個(gè)問(wèn)題。
1、為什么會(huì)在常量池中創(chuàng)建字符串對(duì)象。
2、java虛擬機(jī)的內(nèi)存結(jié)構(gòu)。
先來(lái)看第一個(gè)問(wèn)題。
為什么會(huì)在常量池中創(chuàng)建字符串對(duì)象? 字符串在所有編程語(yǔ)言中都是最常用的類型,其他的數(shù)據(jù)類型都可以轉(zhuǎn)換為字符串類型,像int、long等基本數(shù)據(jù)類型和String都是可以互相轉(zhuǎn)換的。為了提高字符串的使用效率,jvm虛擬機(jī)中特別開辟了一個(gè)常量池的內(nèi)存空間,用于存儲(chǔ)基本數(shù)據(jù)類型的對(duì)象,常量池中的對(duì)象是可以相互共享的,當(dāng)然也包括了String。
我們一般將儲(chǔ)存字符串的常量池成為字符串常量池。字符串常量池中會(huì)存在很多已經(jīng)創(chuàng)建好的字符串對(duì)象,由于String類是用final修飾的,它的值一經(jīng)創(chuàng)建就不可改變,因此我們不用擔(dān)心String對(duì)象共享而帶來(lái)程序的混亂。
我們來(lái)看一段的代碼:
String s1 = "Hello";String s2 = "Hello";
這段代碼只創(chuàng)建一個(gè)對(duì)象,s1和s2是同一個(gè)對(duì)象。根據(jù)上面的解讀,
java String s1 = "Hello"
這行代碼會(huì)先在字符串常量池查找Hello對(duì)象,沒有發(fā)現(xiàn),然后創(chuàng)建Hello對(duì)象并將引用返回給s1。
java String s2 = "Hello"
這行代碼,也先去字符串常量池中查找Hello對(duì)象,發(fā)現(xiàn)已經(jīng)存在,則直接返回給s2。因此s1和s2是同一個(gè)對(duì)象。
接著說(shuō)說(shuō)使用new創(chuàng)建字符串對(duì)象。
通過(guò)new創(chuàng)建字符串對(duì)象,會(huì)在堆中開辟一塊新的內(nèi)存空間,存儲(chǔ)String字符串對(duì)象,因此使用new方式都會(huì)生成新的字符串對(duì)象,不管字符串的內(nèi)容是否一致,使用new創(chuàng)建字符串時(shí)存在堆中,堆中的對(duì)象會(huì)被回收,而使用“=”創(chuàng)建字符串對(duì)象,是存放在常量池中,不會(huì)被回收,因此建議使用“=”的方式創(chuàng)建字符串對(duì)象,避免不必要的java對(duì)象創(chuàng)建和銷毀的開銷。
我們來(lái)看下面的創(chuàng)建字符串對(duì)象時(shí)的內(nèi)存結(jié)構(gòu)圖:
s1和s2是通過(guò)“=”創(chuàng)建的字符串對(duì)象,它們的內(nèi)存地址都一樣,s3是使用new方式創(chuàng)建的字符串對(duì)象,s3和s1、s2的內(nèi)存地址不一樣。
現(xiàn)在接著看第二個(gè)問(wèn)題。
java虛擬機(jī)的內(nèi)存結(jié)構(gòu) 虛擬機(jī)內(nèi)存結(jié)構(gòu)是一個(gè)很復(fù)雜的問(wèn)題,這里只能講一個(gè)大概,主要講各個(gè)內(nèi)存區(qū)域的作用。
java虛擬機(jī)由類加載器、運(yùn)行時(shí)數(shù)據(jù)區(qū)和執(zhí)行引擎構(gòu)成。如下圖所示:
平時(shí)我們說(shuō)的java虛擬機(jī)內(nèi)存結(jié)構(gòu),就是講運(yùn)行時(shí)數(shù)據(jù)區(qū)。
java虛擬機(jī)在執(zhí)行java程序時(shí),會(huì)將內(nèi)存分為幾個(gè)區(qū)域:程序計(jì)數(shù)器、方法區(qū)、虛擬機(jī)棧、本地方法棧、堆。
其中,方法區(qū)和堆是線程共享,程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧時(shí)線程不共享。
1、程序計(jì)數(shù)器 只要學(xué)過(guò)匯編語(yǔ)言,對(duì)這個(gè)程序計(jì)數(shù)器都好理解,就是記錄下一條將要執(zhí)行的字節(jié)碼指令。
通過(guò)操作系統(tǒng)知識(shí)我們知道啟動(dòng)一個(gè)程序時(shí),就會(huì)創(chuàng)建一個(gè)進(jìn)程,因此在執(zhí)行java程序時(shí),就會(huì)創(chuàng)建一個(gè)進(jìn)程,java虛擬機(jī)就是一個(gè)進(jìn)程。
一個(gè)進(jìn)程中由多個(gè)線程組成,在任何一個(gè)時(shí)刻,java虛擬機(jī)只能執(zhí)行一條線程中的指令。
java虛擬機(jī)通過(guò)讀取某一個(gè)線程中的程序計(jì)數(shù)器決定該線程需要執(zhí)行哪個(gè)基礎(chǔ)功能,例如循環(huán)、讀取數(shù)據(jù)庫(kù)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等。
因此每個(gè)線程的程序計(jì)數(shù)器是相互獨(dú)立,互不影響的。
2、java虛擬機(jī)棧 就是我們常說(shuō)的java棧,在執(zhí)行方法時(shí),會(huì)在java棧中創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作數(shù)棧、方法出口等信息。
局部變量表中又會(huì)存放執(zhí)行方法需要的boolean、char等各種基本數(shù)據(jù)類型,對(duì)象引用等。局部變量表大小在代碼編譯期間就已經(jīng)確定。java棧也是線程私有。
創(chuàng)建線程時(shí)同步創(chuàng)建java棧,線程結(jié)束,java棧也同時(shí)銷毀,釋放占用的內(nèi)存。
3、本地方法棧 和java虛擬機(jī)棧功能類似,有的虛擬機(jī)會(huì)將java虛擬機(jī)棧和本地方法棧合并。本地方法棧主要為虛擬機(jī)執(zhí)行Native方法提供服務(wù)。
4、java堆 虛擬機(jī)中最大的一塊內(nèi)存區(qū)域,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,主要用于存放對(duì)象實(shí)例,這塊內(nèi)存區(qū)域由所有線程共享。這個(gè)區(qū)域內(nèi)的對(duì)象,可以被所有的線程訪問(wèn)。
這個(gè)區(qū)域也是java虛擬機(jī)重點(diǎn)管理的對(duì)象,當(dāng)這塊區(qū)域中的對(duì)象沒有被引用,達(dá)到回收標(biāo)準(zhǔn)時(shí),就會(huì)被java垃圾收集器回收,釋放占用的內(nèi)容空間。
java堆分為新生代和老年代,新生代又分為Eden空間、From Survivor空間和To Survivor空間。
使用new操作創(chuàng)建對(duì)象時(shí),就會(huì)在這個(gè)區(qū)域開辟一塊內(nèi)存用于存儲(chǔ)對(duì)象。
上面提到的
java String str1 = new String("Hello")
創(chuàng)建字符串,就會(huì)在java堆中開辟一塊內(nèi)存用于存儲(chǔ)str1對(duì)象。
5、方法區(qū) 方法區(qū)主要存儲(chǔ)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量等數(shù)據(jù),我們也將這個(gè)內(nèi)存區(qū)域稱為永久代,這個(gè)區(qū)域不會(huì)進(jìn)行內(nèi)存回收。
方法區(qū)和java堆一樣,所有線程共享。
方法區(qū)中包含一個(gè)運(yùn)行時(shí)常量池,上面提到的
java String str = "Hello"
創(chuàng)建字符串,就是在運(yùn)行時(shí)常量池中創(chuàng)建“Hello”對(duì)象。
小結(jié):
1、兩種創(chuàng)建字符串對(duì)象的差異。
2、java虛擬機(jī)內(nèi)存區(qū)域的作用。
關(guān)鍵詞:虛擬,結(jié)構(gòu),試題,深入