【Java面試題】如何回答GC相關問題

JacobGo發表於2017-10-28

這裡借用知乎網友@郭無心 的回答。侵刪


作者:郭無心
連結:https://www.zhihu.com/question/35164211/answer/68265045
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

一個面試官對面試問題的分析
這個帖子的背景是今晚看到je上這張貼:大家都來說說自己最討厭的面試題目吧。,心血來潮寫下的文字,如果能拋磚引玉,能有其他面試官分析一下自己面試時問的問題,那或許是件很有意義的事情。 


在公司當技術面試官幾年間,從應屆生到工作十幾年的應聘者都遇到過。先表達一下我自己對面試的觀點:

1.筆試、面試去評價一個人肯定是不夠準確的,瞭解一個人最準確的方式就是“路遙知馬力,日久見人心”。通過一、二個小時內的做題、交流,只是沒有其他辦法下進行的無奈之舉,所以通過了面試不代表有多成功,沒通過也不代表有多失敗。
2.好的面試官本身交談的時候就不應當把自己一個居高臨下的角色上,應當把自己和應聘者當做兩個做技術的人平等的交流,把自己當作權威往往就會受到觀點的角度、語言表達、工作領域的慣性的制約。
3.好的考察題目則是大家能經常接觸,不同層次的人能有不同層次的答案,能從問題引申出後面繼續討論的話題。

舉個例子拋磚引玉,下面這個問題是我以前常問的,從應屆生到工作十幾年的人都問過:
“地球人都知道,Java有個東西叫垃圾收集器,它讓建立的物件不需要像c/cpp那樣delete、free掉,你能不能談談,GC是在什麼時候,對什麼東西,做了什麼事情?”

我自己分析一下這個問題,首先是“什麼時候”,不同層次的回答從低到高排列:

1.系統空閒的時候。
分析:這種回答大約佔30%,遇到的話一般我就會準備轉向別的話題,譬如演算法、譬如SSH看看能否發掘一些他擅長的其他方面。

2.系統自身決定,不可預測的時間/呼叫System.gc()的時候。
分析:這種回答大約佔55%,大部分應屆生都能回答到這個答案,起碼不能算錯誤是吧,後續應當細分一下到底是語言表述導致答案太籠統,還是本身就只有這樣一個模糊的認識。

3.能說出新生代、老年代結構,能提出minor gc/full gc
分析:到了這個層次,基本上能說對GC運作有概念上的瞭解,譬如看過《深入JVM虛擬機器》之類的。這部分不足10%。

4.能說明minor gc/full gc的觸發條件、OOM的觸發條件,降低GC的調優的策略。
分析:列舉一些我期望的回答:eden滿了minor gc,升到老年代的物件大於老年代剩餘空間full gc,或者小於時被HandlePromotionFailure引數強制full gc;gc與非gc時間耗時超過了GCTimeRatio的限制引發OOM,調優諸如通過NewRatio控制新生代老年代比例,通過MaxTenuringThreshold控制進入老年前生存次數等……能回答道這個階段就會給我帶來比較高的期望了,當然面試的時候正常人都不會記得每個引數的拼寫,我自己寫這段話的時候也是翻過手冊的。回答道這部分的小於2%。

PS:加起來不到100%,是因為有確實少數直接說不知道,或者直接拒絕回答的= =#

分析第二個問題:“對什麼東西”:

1.不使用的物件。
分析:相當於沒有回答,問題就是在問什麼物件才是“不使用的物件”。大約佔30%。

2.超出作用域的物件/引用計數為空的物件。
分析:這2個回答站了60%,相當高的比例,估計學校教java的時候老師就是這樣教的。第一個回答沒有解決我的疑問,gc到底怎麼判斷哪些物件在不在作用域的?至於引用計數來判斷物件是否可收集的,我可以會補充一個下面這個例子讓面試者分析一下obj1、obj2是否會被GC掉?
class C{
public Object x;
}
C obj1、obj2 = new C();
obj1.x = obj2;
obj2.x = obj1;
obj1、obj2 = null;

3.從gc root開始搜尋,搜尋不到的物件。
分析:根物件查詢、標記已經算是不錯了,小於5%的人可以回答道這步,估計是引用計數的方式太“深入民心”了。基本可以得到這個問題全部分數。
PS:有面試者在這個問補充強引用、弱引用、軟引用、幻影引用區別等,不是我想問的答案,但可以加分。

4.從root搜尋不到,而且經過第一次標記、清理後,仍然沒有復活的物件。
分析:我期待的答案。但是的確很少面試者會回答到這一點,所以在我心中回答道第3點我就給全部分數。

最後由一個問題:“做什麼事情”,這個問發揮的空間就太大了,不同年代、不同收集器的動作非常多。

1.刪除不使用的物件,騰出記憶體空間。
分析:同問題2第一點。40%。

2.補充一些諸如停止其他執行緒執行、執行finalize等的說明。
分析:起碼把問題具體化了一些,如果像答案1那樣我很難在回答中找到話題繼續展開,大約佔40%的人。
補充一點題外話,面試時我最怕遇到的回答就是“這個問題我說不上來,但是遇到的時候我上網搜一下能做出來”。做程式開發確實不是去鍛鍊茴香豆的“茴”有幾種寫法,不死記硬揹我同意,我不會糾語法、單詞,但是多少你說個思路呀,要直接回答一個上網搜,我完全沒辦法從中獲取可以評價應聘者的資訊,也很難從回答中繼續發掘話題展開討論。建議大家儘量回答引向自己熟悉的,可討論的領域,展現給面試官最擅長的一面。

3.能說出諸如新生代做的是複製清理、from survivor、to survivor是幹啥用的、老年代做的是標記清理、標記清理後碎片要不要整理、複製清理和標記清理有有什麼優劣勢等。
分析:也是看過《深入JVM虛擬機器》的基本都能回答道這個程度,其實到這個程度我已經比較期待了。同樣小於10%。

4.除了3外,還能講清楚序列、並行(整理/不整理碎片)、CMS等蒐集器可作用的年代、特點、優劣勢,並且能說明控制/調整收集器選擇的方式。
分析:同上面2個問題的第四點。

最後介紹一下自己的背景,在一間不大不小的上市軟體公司擔任平臺架構師,有3年左右的面試官經驗,工作主要方向是大規模企業級應用,參與過若干個億元級的專案的底層架構工作。



-------------------------------------------------------------------------------------------------------------------------
先提出一些問題在這裡吧!
①Java和C++在記憶體分配和管理上有什麼區別?

Java與C++之間有一堵由動態記憶體分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裡面的人想出來。
對於從事C和C++程式開發的開發人員來說,在記憶體管理領域,他們既是擁有最高權利的皇帝,也是從事最基礎工作的勞動人民-----既擁有每一個物件的所有權,又擔負著每一個物件從生命開始到終結的維護責任。
對於Java程式設計師來說,虛擬機器的自動記憶體分配機制的幫助下,不再需要為每一個new操作去寫配對的delete/free程式碼,而且不容易出現記憶體洩露和記憶體溢位問題,看起來由虛擬機器管理記憶體一切都很美好。不過,也正是因為Java程式設計師把記憶體控制的權利交給Java虛擬機器,一旦出現記憶體洩露和溢位方面的問題,如果不瞭解虛擬機器是怎樣使用記憶體的,那排查錯誤將會是一項異常艱難的工作。

並且好的Java程式在編寫的時候肯定要考慮GC的問題,怎樣定義static物件,怎樣new物件效率更高等等問題,簡稱面向GC的程式設計

也可以說Java的記憶體分配管理是一種託管的方式,託管於JVM。

C++經過編譯時直接編譯成機器碼,而Java是編譯成位元組碼,由JVM解釋執行。

C++是編譯型語言,而Java兼具編譯型和解釋型語言的特點。

②Java虛擬機器規範將JVM虛擬機器所管理的記憶體分為幾部分?


1. 程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所執行位元組碼的行號指示器。是執行緒私有,生命週期與執行緒相同。
2. Java虛擬機器棧(Java Virtual Machine Stacks)也是執行緒私有的,它的生命週期與執行緒相同。
Java虛擬機器棧描述的是Java方法(區別於native的本地方法)執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變數表、操作棧、動作連結、方法出口等資訊。每個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。
3. 本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則為虛擬機器所使用到的Native方法服務。

4. 方法區

(3)有哪些方法可以判斷一個物件已經可以被回收,JVM怎麼判斷一個物件已經消亡可以被回收?
①引用計數演算法
給物件中新增一個引用計數器,每當有一個地方引用它時,計數器就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的物件就是不可能再被使用的。
Java語言沒有選用引用計數法來管理記憶體,因為引用計數法不能很好的解決迴圈引用的問題。
②根搜尋演算法
在主流的商用語言中,都是使用根搜尋演算法來判定物件是否存活的。
GC Root Tracing 演算法思路就是通過一系列的名為"GC Roots"的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連,即從GC Roots到這個物件不可達,則證明此物件是不可用的。



比如上圖,左邊的物件都是存活的,右邊的都是可以回收的。

(4)那些物件可以作為GC Roots?
虛擬機器棧(棧幀中的本地變數表)中的引用的物件
方法區中的類靜態屬性引用的物件
方法區中的常量引用的物件
本地方法棧中JNI(Native方法)的引用物件

(5)Java程式碼編譯的結果是什麼?
是位元組碼檔案.class

(6) 怎麼理解Java語言的平臺無關性?語言無關性?通過什麼方法實現的?


(7)Java中的static變數和static方法在JVM執行中記憶體的分配管理有什麼不同和一般變數方法?
          靜態物件                非靜態物件 
擁有屬性: 是類共同擁有的           是類各物件獨立擁有的
記憶體分配: 記憶體空間上是固定的        空間在各個附屬類裡面分配 
分配順序: 先分配靜態物件的空間      繼而再對非靜態物件分配空間,也就是初 始化順序是先靜態                      


(8)Java類的載入過程?


(9)在heap中沒有類例項的時候,類資訊還存在於JVM嗎? 存在於什麼地方?

----------------------------------------------------------------------------------------------------------------------------

HotSpot JVM (下簡稱JVM)的記憶體管理
JVM將堆分成了 二個大區 Young 和 Old 如下圖:

<img src="https://pic3.zhimg.com/50/41b0c5f5c0f2f30ed5ef5f699597511e_hd.png" data-rawwidth="556" data-rawheight="326" class="origin_image zh-lightbox-thumb" width="556" data-original="https://pic3.zhimg.com/41b0c5f5c0f2f30ed5ef5f699597511e_r.png">而Young 區又分為

而Young 區又分為 Eden、Servivor1、Servivor2, 兩個Survivor 區相對地作為為From 和 To 邏輯區域, 當Servivor1作為 From 時 , Servivor2 就作為 To, 反之亦然
關於為什麼要這樣區分Young(將Young區分為Eden、Servivor1、Servivor2以及相對的From和To ),這要牽涉到JVM的垃圾回收演算法的討論。
1)因為引用計數法無法解決迴圈引用問題,JVM並沒有採用這種演算法來判斷物件是否存活。
2)JVM一般採用GCRoots的方法,只要從任何一個GCRoots的物件可達,就是不被回收的物件
3)判斷了物件生死,怎麼進行記憶體的清理呢?
4)標記-清除演算法,先標記那些要被回收的物件,然後進行清理,簡單可行,但是①標記清除效率低,因為要一個一個標記和清除②造成大量不連續的記憶體碎片,空間碎片太多可能會導致當程式在以後的執行過程中需要分配較大物件的時候無法找到足夠的連續記憶體而不得不觸發另一次垃圾收集動作。
5)採用複製收集演算法:將可用記憶體按照容量分為大小相等的兩塊,每次只是使用其中的一塊。當這一塊的記憶體用完了,就將可用記憶體中



上面的過程就解釋了為什麼我們的Yong記憶體需要分為三塊,Eden,Survivor1,Survivor2以及FROM TO相對使用的用法



 因此當Eden區滿的時候 GC執行,這時會將 Eden 區和 From 區中還被引用的物件會被移到 To區 ,個別大物件和部分From物件在To已滿的情況下會被放到Old區,如下圖:

HotSpot VM 記憶體堆的兩個Servivor區


----------------------上面的部分內容來自
http://www.iteye.com/topic/894148

帖子裡面題主提了一個問題:
第一次發貼,有什麼問題請大家指正一下。有個地方不太清楚 ,為什麼需要From 和 To 兩個平行的區呢,為什麼不直接從Survivor 移到 Old? 這樣設計的好處是什麼?難道是因為在移動物件的時候需要壓縮調整物件空間,所以這種整體移動的設計會快一點嗎?希望大家一起來討論一下 ^_^

之所以要分兩個Survivor,而不是直接從Survivor直接移到old區域,原因是old區域內的物件都是經過若干次yong GC之後存活下來的物件,並不是每一次yong GC存活下來的物件都需要移動到old區域內,所以需要Survivor1和Survivor2來保證Yong記憶體中的複製演算法的實行,提高清除效率。

相關文章