面試時候總喜歡問的 JVM 要點在哪?
本文轉載自微信公眾號「Java極客技術」,作者鴨血粉絲 Tang。轉載本文請聯系Java極客技術公眾號。
面試的時候,很多面試官問 JVM 的時候,我們作為一個開發者,很多時候很難 Get 到面試官提問的要點,因為 JVM 確實太多了,從程序計數器開始,然后堆,然后棧,但是面試的時候卻總是回答不好這個問題,很多情況就是沒有系統的去看過所以回答面試題的時候,會出現語無倫次,這一塊內容,那邊一塊內容,總是回答不好,幾天阿粉就來分享給大家一個 JVM 的面試教程,對你有用的話,點贊關注和收藏一波。
你對 JVM 了解么?
首先,問這個問題的,一般都是問完了一些基礎了,這時候需要你自己從頭開始說 JVM 了,很多人實際上想到就是垃圾回收機制,確實,沒錯,但是,如果你直接就開始說是不是垃圾回收機制的時候,就已經有點答非所問了。
為什么這么說,因為 JVM 的垃圾回收機制,都是發生在 堆內存 的,但是,JVM 的劃分可不是只要堆內存的,這時候回答應該怎么回答?
** JVM 的內部結構,最主要的內部結構是什么!**
JVM 分成了兩個部分
1.線程共享區域
2.線程私有區域
線程共享區域包含:堆(Heap)、方法區
線程私有區域包含:程序計數器、虛擬機棧(Stack)、本地方法棧
因為 JVM ,那可是不單單只有 堆(Heap) 的存在呀,其他的存在也是不可缺少的,為什么阿粉要這么說呢?
因為有些面試官會問 JVM 的類加載機制 你了解么?
如果你只是了解了垃圾回收機制的話,那你這個問題,是不是有點麻了,有點懵了,這不就芭比Q 了么?
那么 JVM 的類加載機制 是個什么呢?
回答:
首先通過類加載器(ClassLoader)會把 .class字節碼文件加載到內存中——運行時數據區(Runtime Data Area),而字節碼文件只是 JVM 的一套指令集規范,并不能直接交給底層操作系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將字節碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native Interface)來實現整個程序的功能。
跑偏了,我們繼續回答上一個問題,既然你說你了解了,你也回答了都有哪些內部結構了,是不是就該說說這些內容是干啥的了,對,沒錯,就是這么回答。
- 程序計數器:記錄線程執行的位置,方便線程切換后再次執行
- 虛擬機棧(Stack):每個線程在創建時都會創建一個虛擬機棧,其內部保存一個個的棧幀(Stack Frame),對應著一次次的 Java 方法調用
- 本地方法棧:是為了執行native方法所服務的
說完這個,沒啥事別停頓,如果你停頓了,這時候面試官很有可能接著去問你棧的一些特性,你本身是想說垃圾回收機制的,總不能被帶跑偏吧,所以,繼續往下說。
- 方法區 :線程共享,存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等等
最后我們再說堆(Heap)
堆是 JVM 中最主要的區域了,因為堆(Heap)是 Java 虛擬機所管理的內存中最大的一塊。
唯一目的就是儲存對象實例和數組(JDK7 已把字符串常量池和類靜態變量移動到 Java 堆),幾乎所有的對象實例都會存儲在堆中分配。
但是呢,隨著 JIT 編譯器發展,逃逸分析、棧上分配、標量替換等優化技術導致并不是所有對象都會在堆上分配。
這時候,一般面試官都會開始提問了,就會讓你具體的說說堆內存。
Java Heap 堆
Java 堆是垃圾收集器管理的主要區域。堆內存分為新生代 (Young) 和老年代 (Old)
什么是新生代?
主要是用來存放新生的對象。一般占據堆空間的1/3,由于頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。
什么是老年代?
老年代的對象比較穩定,所以MajorGC不會頻繁執行。
那么我們在分別來介紹一下 JVM 的新生代 和 老年代,就這兩個,足夠你和面試官聊上十幾分鐘的內容了。
JVM 的新生代(垃圾回收機制)
新生代分為Eden區、ServivorFrom、ServivorTo三個區。
- Eden區Java新對象的出生地(如果新創建的對象占用內存很大則直接分配給老年代)。當Eden區內存不夠的時候就會觸發一次MinorGc,對新生代區進行一次垃圾回收。
- ServivorFrom區上一次GC的幸存者,作為這一次GC的被掃描者。當JVM無法為新建對象分配內存空間的時候(Eden區滿的時候),JVM觸發MinorGc
- ServivorTo區
保留了一次MinorGc過程中的幸存者。
那么新生代會使用什么樣子的垃圾回收機制呢?
我們每次new對象的時候都會先在新生代的Enden區放著也就是最開始 是這樣子的
然后在Enden用完的時候里面會出現待回收的
然后就來了把存活的對象復制放到Survior1(from)中,待回收的等待給他回收掉 就是這樣的
然后把Enden區清空回收掉
這樣的話 第一次GC就完成了,下面再往下走
當Enden充滿的時候就會再次GC
先是這個樣子的
然后會把 Enden和Survoir1中的內容復制到Survior中,
然后就會把Enden和Survior進行回收
然后從Enden中過去的就相當于次數少的,而從Survior1中過去的就相當于移動了2次
這樣新生代的GC就執行了2次了,
當Enden再次被使用完成的時候,就會從Survior2復制到Survior1中,
接下來是連圖
經過回收之后Surior1就變了,1對象是從Enden直接復制過來的,2對象是Enden-->Survior2-->Survior1 ,3對象則是從Enden-->Surivior1-->Survior2-->Survior1 復制過來的,這樣一步一步的執行下去的時候,就是新生代的GC。
這就是新生代采用的 GC ,如果你需要給面試官解釋,那么你就得熟練的記住這個圖,為什么這么說,因為只有你掌握了這個圖,那么你絕對會把這個復制算法給面試官講述的明明白白。
既然我們都知道了這個復制算法了,那么他到底有什么缺點呢?
- 堆利用效率低 這是最明顯的,空間都被劈成兩半了,一次永遠只能用一半就得搬家
- 遞歸調用 在對子對象進行復制時,使用了遞歸方法,可能導致棧溢出
但是我們也得吹一下復制算法的牛逼的地方呀。
吞吐量高所謂吞吐量就是搜索活動對象的時間比上搜索堆時間,越高說明你的有效搜索占比越高,不難看出,我們都是從根開始,搜索的全部是活動對象,并沒有浪費時間去搜索垃圾對象。這個優勢在堆越大的場景下越明顯。
沒有碎片
在將活動對象復制到To空間時,他們都是緊挨著的,然后清空From時全部清空,完全沒有碎片的可能。
這也是新生代使用的垃圾回收的算法。
JVM 的老年代(垃圾回收機制)
老年代的垃圾回收機制,采用的則是和新生代不一樣的方式,有些人稱之為FullGC,而FullGC出現的原因則是:在新生代如果說存在的對象或者說新創建 出來的對象由于某些原因需要移動到老年代中,但是老年代中壓根就沒有這么大的內存空間去容納這個對象, 那么就會引發一次FullGC,如果在執行完FullGC之后,還是沒有辦法給這些對象分配內存,那么涼了,該拋出異常了,異常類型就是OutOfMemoryError。
而FullGC使用的是和MinorGC不一樣的算法,它使用的是標記清除算法,聽名字,挺好理解的,來波圖示解析一波。深入了解JVM一書中的圖示是這個樣子的,
圖示是不是看著也挺明確,先標記,然后在刪除。
- 標記(Mark)過程:找到所有的可以訪問的對象,做個指定的標記。
- 清除(Swep)過程:遍歷堆內存,把未標記的對象進行一個回收。
在了解了這個之后,我們還得說一個概念,那就是GC Root,Root我們可以理解成一個根節點就像這個樣子
上圖中的a,b,c,d,就是活著的對象,如果說存在這引用,比如說b引用的a,那么a他就是屬于活著的對象。當我們老年代內存區中的有效的內存空間不夠的時候,那么這時候整個世界都要安靜下來了(stop the world),這時候就要開始準備進行垃圾回收了。
- 標記:遍歷所有的GC Roots,然后將所有GC Roots可達的對象標記為存活的對象。就是我們圖中所標記的a,b,c,d.?清除:清除的過程將遍歷堆中所有的對象,將沒有標記的對象全部清除掉。也就是說,如果內存不夠,GC線程就會被觸發然后將程序暫停,隨后將依舊存活的對象標記一遍,最后再將堆中所有沒被標記的對象全部清除掉,接下來便讓程序繼續恢復運行。
流程圖就像這個樣子的 初始下的老年代中的對象狀態
這時候都是沒有被標記的狀態,接下來內存不夠,GC線程停止,開始進行標記了
按照根節點開始遍歷 標記的abcdeh都是存活的對象,接下來開始標記。
接下來就是清除數據了
清楚完成之后還有就是把標記去除掉,可以下次進行標記清除的時候繼續清除
其實這個阿粉的老讀者肯定看過,因為很早之前阿粉就畫出過這個圖。
這樣標記清除就執行完畢了。
這時候不吹不黑,肯定會有優缺點,不然為啥不采用其他的方法呢?畢竟 JVM 肯定是會選擇最適合自己的方式來進行 GC 的。
缺點清除后的堆內存由于空間不連續,即內存碎片化,若下一次需要分配對象的內存大于碎片空間,這樣會提前觸發GC,當提前觸發的GC回收后,空間還是不足就會出現OOM等錯誤。
時間問題:由于分為兩個過程(標記、清除),當堆內可回收對象較多時,該算法需要進行大量的標記與清除,這里就產生一個問題,隨著可回收對象的的增多,標記和清除的效率就會下降;再者由于空間不連續導致每次再次分配都要遍歷空閑列表。
有點
實現簡單,與保守式GC算法兼容 這阿粉真的說不上他其他的優點了,除了能夠解決引用計數算法帶來的不能清除循環引用的問題外,阿粉實在不知道了。
關于 JVM 的知識要點,你學會了么?