就寫了一行代碼,被問了這麼多問題

面試官:如何在一個方法中創建一個局部byte類型數組?

小白:(是不是太基礎了,暗笑)byte[] arrays = new byte[1024]。

面試官:這個局部arrays變量指向的數組對象什麼時候會被GC回收?

小白:沒有變量引用這個數組對象,或者arrays在虛擬機棧中的局部變量表的局部變量空間(Slot)被重用,發生垃圾回收時將會被回收掉。

面試官:數組對象沒有被變量引用會被GC回收,爲什麼?

小白:JVM通過一系列被稱爲"GC Roots"的對象引用作爲起始點,通過引用關係遍歷對象,能被遍歷到的(可到達的)對象就被判定爲存活對象,沒有被遍歷到的(不可到達的)對象就被判定爲死亡對象,找出所有存活對象來把其它對象判定爲可回收對象,這就是可達性分析算法。當這個局部arrays變量所在的方法被執行時,會在當前線程的Java虛擬機棧中創建一個棧幀,這個棧幀的局部變量表中會存儲arrays變量所指向的數組指針,當設置arrays=null,也就是arrays不再引用這個數組對象,arrays和這個數組對象之間的引用關係就斷掉了,發生垃圾回收時,以Java虛擬機棧的棧幀中裏的引用類型的變量爲"GC Roots”,遍歷引用關係,發現這個數組對象和"GC Roots”引用鏈之間沒有關聯了,也就是不可達,即被標識爲可回收對象,等待被回收。

面試官:除了你剛剛說的Java虛擬機棧的棧幀裏的引用類型局部變量可以作爲"GC Roots”,還有哪些也可以作爲"GC Roots”?

小白:當前所有正在被調用的方法裏的引用類型的參數、局部變量和臨時值;Java類的引用類型靜態變量;所有當前被啓動類加載器或系統類加載器加載的Java類,例如如rt.jar中的java.util.*;Java類的運行時常量池裏的引用類型常量;String常量池裏的引用;本地方法棧中JNI的引用;虛擬機裏的一些靜態數據結構裏指向GC堆裏的對象的引用,例如說HotSpot VM裏的Universe裏有很多這樣的引用。

面試官:當一個對象被標識爲可回收對象就一定會被回收掉嗎?

小白:不一定。一個對象被標識爲可回收對象後,還需要經過再次篩選,即查看這個對象有沒有覆蓋finalize()方法,或finalize()方法有沒有被虛擬機執行過,如果沒有覆蓋finalize()方法或finalize()方法已經被虛擬機執行過,那麼這個對象將會被回收掉,否則這個對象將會被放到一個叫F-Queue的隊列中,這個隊列中對象的finalize()方法將會被虛擬機創建的低優先級的Finalizer線程執行,在執行finalize()方法的過程中,只要這個對象和GC Roots引用鏈產生關聯,即再次被GC Roots集合中的成員引用,那麼它將被標記爲不可回收對象,繼續存活。

面試官:剛剛一直說到垃圾回收,那麼Minor GC、Major GC和Full GC有什麼區別?

小白:Minor GC指新生代GC,即發生在新生代(包括Eden區和Survivor區)的垃圾回收操作,當新生代無法爲新生對象分配內存空間的時候,會觸發Minor GC。因爲新生代中大多數對象的生命週期都很短,所以發生Minor GC的頻率很高,雖然它會觸發stop-the-world,但是它的回收速度很快。Major GC清理Tenured區,用於回收老年代,出現Major GC通常會出現至少一次Minor GC。Full GC是針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局範圍的GC。Full GC不等於Major GC,也不等於Minor GC+Major GC,發生Full GC需要看使用了什麼垃圾收集器組合,才能解釋是什麼樣的垃圾回收。

面試官:垃圾回收算法有哪些?

小白:標記-清除算法分爲兩部分,標記和清除。首先標記出所有需要被回收的對象,然後在標記完成後統一回收掉所有被標記的對象。這個算法簡單,但是有兩個缺點:一是標記和清除的效率不是很高;二是標記和清除後會產生很多的內存碎片,導致可用的內存空間不連續,當分配大對象的時候,沒有足夠的空間時不得不提前觸發一次垃圾回收。

複製算法將可用的內存空間分爲大小相等的兩塊,每次只是用其中的一塊,當這一塊被用完的時候,就將還存活的對象複製到另一塊中,然後把原已使用過的那一塊內存空間一次回收掉。這個算法常用於新生代的垃圾回收。複製算法解決了標記-清除算法的效率問題,以空間換時間,但是當存活對象非常多的時候,複製操作效率將會變低,而且每次只能使用一半的內存空間,利用率不高。

標記-整理算法分爲三部分:一是標記出所有需要被回收的對象;二是把所有存活的對象都向一端移動;三是把所有存活對象邊界以外的內存空間都回收掉。

標記-整理算法解決了複製算法多複製效率低、空間利用率低的問題,同時也解決了內存碎片的問題。

分代收集算法根據對象生存週期的不同將內存空間劃分爲不同的塊,然後對不同的塊使用不同的回收算法。一般把Java堆分爲新生代和老年代,新生代中對象的存活週期短,只有少量存活的對象,所以可以使用複製算法,而老年代中對象存活時間長,而且對象比較多,所以可以採用標記-清除和標記-整理算法。

面試官:JVM運行時數據區中的方法區可以進行垃圾回收嗎?

小白:方法區和堆一樣,都是線程共享的內存區域,被用於存儲已被虛擬機加載的類信息、即時編譯後的代碼、靜態變量和常量等數據。根據Java虛擬機規範的規定,方法區無法滿足內存分配需求時,也會拋出OutOfMemoryError異常,雖然規範規定虛擬機可以不實現垃圾收集,因爲和堆的垃圾回收效率相比,方法區的回收效率實在太低,但是此部分內存區域也是可以被回收的。方法區的垃圾回收主要有兩種,分別是對廢棄常量的回收和對無用類的回收。當一個常量對象不再任何地方被引用的時候,則被標記爲廢棄常量,這個常量可以被回收。方法區中的類需要同時滿足以下三個條件才能被標記爲無用的類:Java堆中不存在該類的任何實例對象、加載該類的類加載器已經被回收、該類對應的java.lang.Class對象不在任何地方被引用,且無法在任何地方通過反射訪問該類的方法,當滿足上述三個條件的類纔可以被回收,但是並不是一定會被回收,需要參數進行控制,例如HotSpot虛擬機提供了-Xnoclassgc參數進行控制是否回收。

面試官:如果讓你配置JVM新生代和老年代的大小,你如何掌控?

小白:新生代配置原則:

  • 追求響應時間優先

這種需求下,新生代儘可能設置大一些,並通過實際情況調整新生代大小,直至接近系統的最小響應時間。因爲新生代比較大,發生垃圾回收的頻率會比較低,響應時間快速。

  • 追求吞吐量優先

吞吐量優先的應用,在新生代中的大部分對象都會被回收,所以,新生代儘可能設置大。此時不追求響應時間,垃圾回收可以並行進行。

  • 避免設置過小

新生代設置過小,YGC會很頻繁,同時,很可能導致對象直接進入老年代中,老年代空間不足發生FullGC。

老年代配置原則:

  • 追求響應時間優先

這種情況下,可以使用CMS收集器,以獲取最短回收停頓時間,但是其內存分配需要注意,如果設置小了會造成回收頻繁並且碎片變多;如果設置大了,回收的時間會很長。所以,最優的方案是根據GClog分析垃圾回收信息,調整內存大小。

  • 追求吞吐量優先

吞吐量優先通常需要分配一個大新生代、小老年代,將短期存活的對象在新生代回收掉。

推薦閱讀:

面試官:JVM對鎖進行了優化,都優化了啥?

synchronized連環問

如有收穫,請點擊底部右下角"在看",謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章