天天看點

java 常量字元串過長_從一道面試題深入了解java虛拟機記憶體結構

記得剛大學畢業時,為了應付面試,瘋狂的在網上刷JAVA的面試題,很多都靠死記硬背。其中有道面試題,給我的印象非常之深刻,有個大廠的面試官,順着這道題目,一直往下問,問到java虛拟機的知識,最後把我給問住了。 我當時的表情是這樣的:

後來我有機會面試别人了,也按照他的思路出面試題,很多已經工作了2年的程式員,結果也和我當年一樣,都敗在java虛拟機知識上。

我們先看面試題:

String str1 = "hello Alunbar";
String str2 = new String(str1);
           

會建立幾個對象?

網上給出的解釋是建立2個對象,str1對象在常量池中,str2對象在堆中。

下面是我和面試官的對話。

面試官:上面的代碼建立了幾個對象?

我:2個。 面試官:為什麼是2個呢?

我:str1對象在常量池中,str2對象在堆中。用“=”等号建立String對象時,會先從字元串常量池中查找是否已經存在字元串對象,存在就直接傳回引用位址,否則建立字元串對象并傳回引用位址。

面試官:為什麼會在常量池中建立字元串對象?

我:。。。我思考了半分鐘,尴尬的回答不知道。

面試官:說說jvm虛拟機的記憶體結構。 我:。。。我再次面露難色,場面一度非常尴尬。

這次面試結束之後,我就回去瘋狂查找資料,了解jvm虛拟機的相關知識。

這也是我的第一次面試,給我的印象非常之深刻。

下面我們來說說面試官的兩個問題。

1、為什麼會在常量池中建立字元串對象。

2、java虛拟機的記憶體結構。

先來看第一個問題。

為什麼會在常量池中建立字元串對象?

字元串在所有程式設計語言中都是最常用的類型,其他的資料類型都可以轉換為字元串類型,像int、long等基本資料類型和String都是可以互相轉換的。為了提高字元串的使用效率,jvm虛拟機中特别開辟了一個常量池的記憶體空間,用于存儲基本資料類型的對象,常量池中的對象是可以互相共享的,當然也包括了String。

我們一般将儲存字元串的常量池成為字元串常量池。字元串常量池中會存在很多已經建立好的字元串對象,由于String類是用final修飾的,它的值一經建立就不可改變,是以我們不用擔心String對象共享而帶來程式的混亂。

我們來看一段的代碼:

String s1 = "Hello";
String s2 = "Hello";
           

這段代碼隻建立一個對象,s1和s2是同一個對象。根據上面的解讀,

java String s1 = "Hello"

這行代碼會先在字元串常量池查找Hello對象,沒有發現,然後建立Hello對象并将引用傳回給s1。

java String s2 = "Hello"

這行代碼,也先去字元串常量池中查找Hello對象,發現已經存在,則直接傳回給s2。是以s1和s2是同一個對象。

接着說說使用new建立字元串對象。

通過new建立字元串對象,會在堆中開辟一塊新的記憶體空間,存儲String字元串對象,是以使用new方式都會生成新的字元串對象,不管字元串的内容是否一緻,使用new建立字元串時存在堆中,堆中的對象會被回收,而使用“=”建立字元串對象,是存放在常量池中,不會被回收,是以建議使用“=”的方式建立字元串對象,避免不必要的java對象建立和銷毀的開銷。

我們來看下面的建立字元串對象時的記憶體結構圖:

java 常量字元串過長_從一道面試題深入了解java虛拟機記憶體結構

s1和s2是通過“=”建立的字元串對象,它們的記憶體位址都一樣,s3是使用new方式建立的字元串對象,s3和s1、s2的記憶體位址不一樣。

現在接着看第二個問題。

java虛拟機的記憶體結構

虛拟機記憶體結構是一個很複雜的問題,這裡隻能講一個大概,主要講各個記憶體區域的作用。

java虛拟機由類加載器、運作時資料區和執行引擎構成。如下圖所示:

java 常量字元串過長_從一道面試題深入了解java虛拟機記憶體結構

平時我們說的java虛拟機記憶體結構,就是講運作時資料區。

java虛拟機在執行java程式時,會将記憶體分為幾個區域:程式計數器、方法區、虛拟機棧、本地方法棧、堆。

java 常量字元串過長_從一道面試題深入了解java虛拟機記憶體結構

其中,方法區和堆是線程共享,程式計數器、虛拟機棧、本地方法棧時線程不共享。

1、程式計數器

隻要學過彙編語言,對這個程式計數器都好了解,就是記錄下一條将要執行的位元組碼指令。

通過作業系統知識我們知道啟動一個程式時,就會建立一個程序,是以在執行java程式時,就會建立一個程序,java虛拟機就是一個程序。

一個程序中由多個線程組成,在任何一個時刻,java虛拟機隻能執行一條線程中的指令。

java虛拟機通過讀取某一個線程中的程式計數器決定該線程需要執行哪個基礎功能,例如循環、讀取資料庫、跳轉、異常處理、線程恢複等。

是以每個線程的程式計數器是互相獨立,互不影響的。

2、java虛拟機棧

就是我們常說的java棧,在執行方法時,會在java棧中建立一個棧幀,用于存儲局部變量表、操作數棧、方法出口等資訊。

局部變量表中又會存放執行方法需要的boolean、char等各種基本資料類型,對象引用等。局部變量表大小在代碼編譯期間就已經确定。java棧也是線程私有。

建立線程時同步建立java棧,線程結束,java棧也同時銷毀,釋放占用的記憶體。

3、本地方法棧

和java虛拟機棧功能類似,有的虛拟機會将java虛拟機棧和本地方法棧合并。本地方法棧主要為虛拟機執行Native方法提供服務。

4、java堆

虛拟機中最大的一塊記憶體區域,虛拟機啟動時建立,主要用于存放對象執行個體,這塊記憶體區域由所有線程共享。這個區域内的對象,可以被所有的線程通路。

這個區域也是java虛拟機重點管理的對象,當這塊區域中的對象沒有被引用,達到回收标準時,就會被java垃圾收集器回收,釋放占用的内容空間。

java堆分為新生代和老年代,新生代又分為Eden空間、From Survivor空間和To Survivor空間。

使用new操作建立對象時,就會在這個區域開辟一塊記憶體用于存儲對象。

上面提到的

java String str1 = new String("Hello")

建立字元串,就會在java堆中開辟一塊記憶體用于存儲str1對象。

5、方法區

方法區主要存儲被虛拟機加載的類資訊、常量、靜态變量等資料,我們也将這個記憶體區域稱為永久代,這個區域不會進行記憶體回收。

方法區和java堆一樣,所有線程共享。

方法區中包含一個運作時常量池,上面提到的

java String str = "Hello"

建立字元串,就是在運作時常量池中建立“Hello”對象。

小結:

1、兩種建立字元串對象的差異。

2、java虛拟機記憶體區域的作用。

繼續閱讀