萬萬沒想到,JVM記憶體結構的面試題可以問的這麼難?
在我的部落格中,之前有很多文章介紹過JVM記憶體結構,相信很多看多我文章的朋友對這部分知識都有一定的瞭解了。
那麼,請大家嘗試著回答一下以下問題:
1、JVM管理的記憶體結構是怎樣的?
2、不同的虛擬機器在實現執行時記憶體的時候有什麼區別?
3、執行時資料區中哪些區域是執行緒共享的?哪些是獨享的?
4、除了JVM執行時記憶體以外,還有什麼區域可以用嗎?
5、堆和棧的區別是什麼?
6、Java中的陣列是儲存在堆上還是棧上的?
7、Java中的物件建立有多少種方式?
8、Java中物件建立的過程是怎麼樣的?
9、Java中的物件一定在堆上分配記憶體嗎?
10、如何獲取堆和棧的dump檔案?
以上10道題,如果您可以全部準確無誤的回答的話,那說明你真的很瞭解JVM的記憶體結構以及記憶體分配相關的知識了,如果有哪些知識點是不瞭解的,那麼本文正好可以幫你答疑解惑。
JVM管理的記憶體結構是怎樣的?
Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。《Java虛擬機器規範》中規定了JVM所管理的記憶體需要包括一下幾個執行時區域:

主要包含了PC暫存器(程式計數器)、Java虛擬機器棧、本地方法棧、Java堆、方法區以及執行時常量池。
各個區域有各自不同的作用,關於各個區域的作用就不在本文中相信介紹了。
但是,需要注意的是,上面的區域劃分只是邏輯區域,對於有些區域的限制是比較鬆的,所以不同的虛擬機器廠商在實現上,甚至是同一款虛擬機器的不同版本也是不盡相同的。
不同的虛擬機器在實現執行時記憶體的時候有什麼區別?
前面提到過《Java虛擬機器規範》定義的JVM執行時所需的記憶體區域,不同的虛擬機器實現上有所不同,而在這麼多區域中,規範對於方法區的管理是最寬鬆的,規範中關於這部分的描述如下:
方法區在虛擬機器啟動的時候建立,雖然方法區是堆的邏輯組成部分,但是簡單的虛擬機器實現可以選擇在這個區域不實現垃圾收集與壓縮。本版本的規範也不限定實現方法區的記憶體位置和程式碼編譯的管理策略。方法區的容量可以是固定的,也可以隨著程式執行的需求動態擴充套件,並在不需要過多的空間時自行收縮。方法區在實際記憶體空間站可以是不連續的。
這一規定,可以說是給了虛擬機器廠商很大的自由。
虛擬機器規範對方法區實現的位置並沒有明確要求,在最著名的HotSopt虛擬機器實現中(在Java 8 之前),方法區僅是邏輯上的獨立區域,在物理上並沒有獨立於堆而存在,而是位於永久代中。所以,這時候方法區也是可以被垃圾回收的。
實踐證明,JVM中存在著大量的宣告短暫的物件,還有一些生命週期比較長的物件。為了對他們採用不同的收集策略,採用了分代收集演算法,所以HotSpot虛擬機器把的根據物件的年齡不同,把堆分位新生代、老年代和永久代。
在Java 8中 ,HotSpot虛擬機器移除了永久代,使用本地記憶體來儲存類元資料資訊並稱之為:元空間(Metaspace)

執行時資料區中哪些區域是執行緒共享的?哪些是獨享的?
在JVM執行時記憶體區域中,PC暫存器、虛擬機器棧和本地方法棧是執行緒獨享的。
而Java堆、方法區是執行緒共享的。但是值得注意的是,Java堆其實還未每一個執行緒單獨分配了一塊TLAB空間,這部分空間在分配時是執行緒獨享的,在使用時是執行緒共享的。
除了JVM執行時記憶體以外,還有什麼區域可以用嗎?
除了我們前面介紹的虛擬機器執行時資料區以外,還有一部分記憶體也被頻繁使用,他不是執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域,他就是——直接記憶體。
直接記憶體的分配不受Java堆大小的限制,但是他還是會收到伺服器總記憶體的影響。
在JDK 1.4中引入的NIO中,引入了一種基於Channel和Buffer的I/O方式,他可以使用Native函式直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer物件作為這塊記憶體的應用進行操作。

堆和棧的區別是什麼?
堆和棧(虛擬機器棧)是完全不同的兩塊記憶體區域,一個是執行緒獨享的,一個是執行緒共享的,二者之間最大的區別就是儲存的內容不同:
堆中主要存放物件例項。
棧(區域性變量表)中主要存放各種基本資料型別、物件的引用。
Java中的陣列是儲存在堆上還是棧上的?
在Java中,陣列同樣是一個物件,所以物件在記憶體中如何存放同樣適用於陣列;
所以,陣列的例項是儲存在堆中,而陣列的引用是儲存在棧上的。

Java中的物件建立有多少種方式?
Java中有很多方式可以建立一個物件,最簡單的方式就是使用new關鍵字。
User user = new User();
除此以外,還可以使用反射機制建立物件:
User user = User.class.newInstance();
或者使用Constructor類的newInstance:
Constructor<User> constructor = User.class.getConstructor();
User user = constructor.newInstance();
除此之外還可以使用clone方法和反序列化的方式,這兩種方式不常用並且程式碼比較複雜,就不在這裡展示了,感興趣的可以自行了解下。
Java中物件建立的過程是怎麼樣的?
對於一個普通的Java物件的建立,大致過程如下:
1、虛擬機器遇到new指令,到常量池定位到這個類的符號引用。
2、檢查符號引用代表的類是否被載入、解析、初始化過。
3、虛擬機器為物件分配記憶體。
4、虛擬機器將分配到的記憶體空間都初始化為零值。
5、虛擬機器對物件進行必要的設定。
6、執行方法,成員變數進行初始化。
Java中的物件一定在堆上分配記憶體嗎?
前面我們說過,Java堆中主要儲存了物件例項,但是,隨著JIT編譯期的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化,所有的物件都分配到堆上也漸漸變得不那麼“絕對”了。
其實,在編譯期間,JIT會對程式碼做很多優化。其中有一部分優化的目的就是減少記憶體堆分配壓力,其中一種重要的技術叫做逃逸分析。
如果JIT經過逃逸分析,發現有些物件沒有逃逸出方法,那麼有可能堆記憶體分配會被優化成棧記憶體分配。

10、如何獲取堆和棧的dump檔案?
Java Dump,Java虛擬機器的執行時快照。將Java虛擬機器執行時的狀態和資訊儲存到檔案。
可以使用在伺服器上使用jmap命令來獲取堆dump,使用jstack命令來獲取執行緒的呼叫棧dump