一文了解JVM面試篇(上)
Java內存區域
1、如何解釋 Java 堆空間及 GC?
當通過 Java 命令啟動 Java 進程的時候,會為它分配內存。內存的一部分用于創建
堆空間,當程序中創建對象的時候,就從對空間中分配內存。GC 是 JVM 內部的一
個進程,回收無效對象的內存用于將來的分配。
2、JVM 的主要組成部分及其作用?
組成部分:

-
JVM 包含兩個子系統和兩個組件,兩個子系統為:Class loader(類裝載)和
Executionengine(執行引擎);
-
兩個組件為 Runtime data area(運行時數據區)、Native Interface(本地接
口)。
-
Class loader(類裝載):
根據給定的全限定名類名(如:java.lang.Object)來裝載class 文件到Runtime data area 中的 method area。
-
Execution engine(執行引擎):執行 classes 中的指令。
-
Native Interface(本地接口):與 native libraries 交互,是其它編程語言交
互的接口。
-
Runtime data area(運行時數據區域):這就是我們常說的 JVM 的內存。
作用:
首先通過編譯器把 Java 代碼轉換成字節碼,類加載器(ClassLoader)再把字節
碼加載到內存中,將其放在運行時數據區(Runtime data area)的方法區內,而
字節碼文件只是 JVM 的一套指令集規范,并不能直接交給底層操作系統去執行,因
此需要特定的命令解析器執行引擎(Execution Engine),將字節碼翻譯成底層系統
指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native
Interface)來實現整個程序的功能。
3、Java 程序運行機制詳細說明
Java 程序運行機制步驟
-
首先利用 IDE 集成開發工具編寫 Java 源代碼,源文件的后綴為.java;
-
再利用編譯器(javac 命令)將源代碼編譯成字節碼文件,字節碼文件的后綴名
為.class;
-
運行字節碼的工作是由解釋器(java 命令)來完成的。

從上圖可以看,java 文件通過編譯器變成了.class 文件,接下來類加載器又將這
些.class 文件加載到 JVM 中。
其實可以一句話來解釋:類的加載指的是將類的.class 文件中的二進制數據讀入到
內存中,將其放在運行時數據區的方法區內,然后在堆區創建一個java.lang.Class
對象,用來封裝類在方法區內的數據結構。
4、JVM 內存模型
Java 虛擬機在執行 Java 程序的過程中會把它所管理的內存區域劃分為若干個不同
的數據區域。這些區域都有各自的用途,以及創建和銷毀的時間,有些區域隨著虛
擬機進程的啟動而存在,有些區域則是依賴線程的啟動和結束而建立和銷毀。Java
虛擬機所管理的內存被劃分為如下幾個區域:

-
程序計數器(Program Counter Register):當前線程所執行的字節碼的行
號指示器,字節碼解析器的工作是通過改變這個計數器的值,來選取下一條
需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功
能,都需要依賴這個計數器來完成;
-
Java 虛擬機棧(Java Virtual Machine Stacks):用于存儲局部變量表、操
作數棧、動態鏈接、方法出口等信息;
-
本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不
過虛擬機棧是服務 Java 方法的,而本地方法棧是為虛擬機調用 Native 方法
服務的;
-
Java 堆(Java Heap):Java 虛擬機中內存最大的一塊,是被所有線程共享
的,幾乎所有的對象實例都在這里分配內存;
-
方法區(Methed Area):用于存儲已被虛擬機加載的類信息、常量、靜態
變量、即時編譯后的代碼等數據。
深拷貝和淺拷貝
-
淺拷貝(shallowCopy)只是增加了一個指針指向已存在的內存地址;
-
深拷貝(deepCopy)是增加了一個指針并且申請了一個新的內存,使這個增
加的指針指向這個新的內存;
使用深拷貝的情況下,釋放內存的時候不會因為出現淺拷貝時釋放同一個內存的錯
誤。
-
淺復制:僅僅是指向被復制的內存地址,如果原地址發生改變,那么淺復制
出來的對象也會相應的改變。
-
深復制:在計算機中開辟一塊新的內存地址用于存放復制的對象。
堆棧的區別
物理地址
-
堆的物理地址分配對對象是不連續的。因此性能慢些。在 GC 的時候也要考
慮到不連續的分配,所以有各種算法。比如,標記-消除,復制,標記-壓縮,分代(即新生代使用復制算法,老年代使用標記——壓縮)。 -
棧使用的是數據結構中的棧,先進后出的原則,物理地址分配是連續的。所
以性能快。
內存分別
-
堆因為是不連續的,所以分配的內存是在運行期確認的,因此大小不固定。一般
堆大小遠遠大于棧。
-
棧是連續的,所以分配的內存大小要在編譯期就確認,大小是固定的。
存放的內容
-
堆存放的是對象的實例和數組。因此該區更關注的是數據的存儲
-
棧存放:局部變量,操作數棧,返回結果。該區更關注的是程序方法的執行。
例如:
-
靜態變量放在方法區
-
靜態的對象還是放在堆
程序的可見度
-
堆對于整個應用程序都是共享、可見的。
-
棧只對于線程是可見的。所以也是線程私有。他的生命周期和線程相同。
Java 中堆和棧的區別
JVM 中堆和棧屬于不同的內存區域,使用目的也不同。棧常用于保存方法幀和局部
變量,而對象總是在堆上分配。棧通常都比堆小,也不會在多個線程之間共享,而堆
被整個JVM 的所有線程共享。
隊列和棧是什么?有什么區別?
隊列和棧都是被用來預存儲數據的。
-
操作的名稱不同。隊列的插入稱為入隊,隊列的刪除稱為出隊。棧的插入稱
為進棧,棧的刪除稱為出棧。
-
可操作的方式不同。隊列是在隊尾入隊,隊頭出隊,即兩邊都可操作。而棧
的進棧和出棧都是在棧頂進行的,無法對棧底直接進行操作。
-
操作的方法不同。隊列是先進先出(FIFO),即隊列的修改是依先進先出的
原則進行的。新來的成員總是加入隊尾(不能從中間插入),每次離開的成員
總是隊列頭上(不允許中途離隊)。而棧為后進先出(LIFO),即每次刪除(出
棧)的總是當前棧中最新的元素,即最后插入(進棧)的元素,而最先插入的
被放在棧的底部,要到最后才能刪除。
虛擬機棧(線程私有)
-
它是描述 java 方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀
(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。
每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到
出棧的過程。
-
棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處
理動態鏈接(Dynamic Linking),方法返回值和異常分派(DispatchException)。
棧幀隨著方法調用而創建,隨著方法結束而銷毀——無論方法是正常完成還是
異常完成(拋出了在方法內未被捕獲的異常)都算作方法束。

程序計數器(線程私有)
一塊較小的內存空間, 是當前線程所執行的字節碼的行號指示器,每條線程都
要有一個獨立的程序計數器,這類內存也稱為“線程私有” 的內存。
正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指
令的地址) 。如果還是 Native 方法,則為空。這個內存區域是唯一一個在
虛擬機中沒有規定任何 OutOfMemoryError 情況的區域。
什么是直接內存?
直接內存并不是 JVM 運行時數據區的一部分, 但也會被頻繁的使用: 在JDK 1.4 引入
的 NIO 提供了基于 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函數庫直接
分配堆外內存, 然后使用 DirectByteBuffer 對象作為這塊內存的引用進行操作(詳見:
Java I/O 擴展), 這樣就避免了在 Java 堆和 Native 堆中來回復制數據, 因此在一些
場景中可以顯著提高性能。


浙公網安備 33010602011771號