從一道面試題深入瞭解java虛擬機器記憶體結構
記得剛大學畢業時,為了應付面試,瘋狂的在網上刷JAVA的面試題,很多都靠死記硬背。其中有道面試題,給我的印象非常之深刻,有個大廠的面試官,順著這道題目,一直往下問,問到java虛擬機器的知識,最後把我給問住了。
我當時的表情是這樣的:
後來我有機會面試別人了,也按照他的思路出面試題,很多已經工作了2年的程式設計師,結果也和我當年一樣,都敗在java虛擬機器知識上。
我們先看面試題:
String str1 = "hello Alunbar";
String str2 = new String(str1);
會建立幾個物件?
網上給出的解釋是建立2個物件,str1物件在常量池中,str2物件在堆中。
下面是我和麵試官的對話。
面試官:上面的程式碼建立了幾個物件?
我:2個。
面試官:為什麼是2個呢?
我:str1物件在常量池中,str2物件在堆中。用“=”等號建立String物件時,會先從字串常量池中查詢是否已經存在字串物件,存在就直接返回引用地址,否則建立字串物件並返回引用地址。
面試官:為什麼會在常量池中建立字串物件?
我:。。。我思考了半分鐘,尷尬的回答不知道。
面試官:說說jvm虛擬機器的記憶體結構。
我:。。。我再次面露難色,場面一度非常尷尬。
這次面試結束之後,我就回去瘋狂查詢資料,瞭解jvm虛擬機器的相關知識。
這也是我的第一次面試,給我的印象非常之深刻。
下面我們來說說面試官的兩個問題。
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物件建立和銷燬的開銷。
我們來看下面的建立字串物件時的記憶體結構圖:
s1和s2是通過“=”建立的字串物件,它們的記憶體地址都一樣,s3是使用new方式建立的字串物件,s3和s1、s2的記憶體地址不一樣。
現在接著看第二個問題。
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虛擬機器記憶體區域的作用