1. 程式人生 > >就寫了一行程式碼,被問了這麼多問題

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

面試官:如何建立一個區域性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新生代和老年代的大小,你如何掌控?
小白: 新生代配置原則:
1.追求響應時間優先
這種需求下,新生代儘可能設定大一些,並通過實際情況調整新生代大小,直至接近系統的最小響應時間。因為新生代比較大,發生垃圾回收的頻率會比較低,響應時間快速。

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

3.避免設定過小
新生代設定過小,YGC會很頻繁,同時,很可能導致物件直接進入老年代中,老年代空間不足發生FullGC。

老年代配置原則:
1.追求響應時間優先
這種情況下,可以使用CMS收集器,以獲取最短回收停頓時間,但是其記憶體分配需要注意,如果設定小了會造成回收頻繁並且碎片變多;如果設定大了,回收的時間會很長。所以,最優的方案是根據GClog分析垃圾回收資訊,調整記憶體大小。

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

關注不迷路,記錄後端開發那些事