Java面試系列02:Java基礎之GC

原文鏈接

1.GC 是什麼? 爲什麼要有 GC?

垃圾收集(Garbage Collection)通常被稱爲“GC”,由虛擬機“自動化”完成垃圾回收工作。既然GC會自動回收,開發人員爲什麼要學習GC和內存分配呢?當需要排查各種內存溢出,內存泄露問題時,當垃圾成爲系統達到更高併發量的瓶頸時,我們就需要對GC的自動回收實施必要的監控和調節。JVM中程序計數器、虛擬機棧、本地方法棧3個區域隨線程而生隨線程而滅。棧幀隨着方法的進入和退出做入棧和出棧操作,實現了自動的內存清理。GC垃圾回收主要集中在堆和方法區,在程序運行期間,這部分內存的分配和使用都是動態的。

2.如何判斷一個對象是否存活?(或者 GC 對象的判定方法)

引用計數算法和可達性分析算法
引用計數算法:
給對象添加一個引用計數器,每當有一個地方引用它時計數器加1,引用釋放時計數減1,當計數器爲0時可以回收。
評價:引用計數算法實現簡單,判斷高效,在微軟COM和Python語言等被廣泛使用,但在主流的Java虛擬機中沒有使用該方法,主要是因爲無法解決對象相互循環引用的問題。
可達性分析算法:
基本思想是通過一系列稱爲“GC Root”的對象(如系統類加載器、棧中的對象、處於激活狀態的線程等)作爲起點,基於對象引用關係,開始向下搜索,所走過的路徑稱爲引用鏈,當一個對象到GC Root沒有任何引用鏈相連,證明對象是不可用的。

3.簡述 Java 垃圾回收機制(垃圾回收算法)

1)標記清除算法
包含“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。標記清除算法是最基礎的收集算法,後續的收集算法都是基於該思路並對其缺點進行改進而得到的。
主要缺點:

  • a. 一個是效率問題,標記和清除過程的效率都不高;
  • b. 空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

2)複製算法
將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊內存用完了,就將還存活着的對象複製到另外一塊上,然後清理掉前一塊。
優點:每次對半區內存回收時、內存分配時就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
缺點:將內存縮小爲一半,性價比低,持續複製長生存期的對象則導致效率低下。
在GC回收過程中,當Eden區滿時,還存活的對象會被複制到其中一個Survivor區(s0);當回收時,會將Eden和使用的Survivor區還存活的對象,複製到另外一個Survivor區(s1),然後對Eden和用過的Survivor區進行清理。如果另外一個Survivor區(s1)沒有足夠的內存存儲時,則會進入老年代。對象每經歷一次複製,年齡加1。由於Eden中的對象屬於像浮萍一樣“瞬生瞬滅”的對象,所以並不需要1:1的比例來分配內存,而是採用了8(Eden):1(s0):1(s1)的比例來分配。而針對那些像“水熊蟲”一樣,歷經多次清理依舊存活的對象,則會進入老年代,而老年的清理算法則採用下面要講到的“標記整理算法”。
在這裏插入圖片描述
3)標記整理算法
標記過程與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。這種算法不既不用浪費50%的內存,也解決了複製算法在對象存活率較高時的效率低下問題。
4)分代收集算法
將Java的堆內存邏輯上分成兩塊,新生代和老年代,針對不同存活週期、不同大小的對象採取不同的垃圾回收策略。
新生代:在新生代中大多數對象都是瞬間對象,只有少量對象存活,複製較少對象即可完成清理,因此採用複製算法
老年代:老年代中的對象,存活率較高,又沒有額外的擔保內存,因此採用標記整理算法。

4.垃圾回收機制的優點和原理,並考慮兩種回收機制(機制看上面的 2.如何判斷一個對象是否存活?)

1)Java語言中一個顯著的特點就是引入了垃圾回收機制,使C++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。
2)垃圾回收可以有效的防止內存泄露,有效的使用內存。
3)垃圾回收器通常是作爲一個單獨的低級別的線程(後臺線程)運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。

5.垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

基本原理:
可達性分析算法:…
垃圾回收器不可以馬上回收內存。
垃圾收集器不可以被強制執行,但程序員可以通過調用System.gc方法來建議執行垃圾收集。記住,只是建議。一般不建議自己寫System.gc,因爲會加大垃圾收集工作量 程序員可以手動執行System.gc(),通知GC運行,但是並不保證GC一定會執行。

6.Java 中會存在內存泄漏嗎,請簡單描述。

所謂內存泄露就是指一個不再被程序使用的對象或變量一直被佔據在內存中。
Java中有垃圾回收機制,它可以保證一對象不再被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內存中清除掉。
由於Java 使用有向圖的方式進行垃圾回收管理,可以消除引用循環的問題,例如有兩個對象,相互引用,只要它們和根進程不可達的,那麼GC也是可以回收它們的。
Java中的內存泄露的情況:
長生命週期的對象持有短生命週期對象的引用就很可能發生內存泄露,儘管短生命週期對象已經不再需要,但是因爲長生命週期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景。

7.深拷貝和淺拷貝

淺拷貝
淺拷貝是按位拷貝對象,它會創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。
如果屬性是基本類型,拷貝的就是基本類型的值。
如果屬性是引用類型,拷貝的就是內存地址 ,兩個變量指向同一個內存地址,因此淺拷貝會帶來數據安全方面的隱患。
深拷貝
基礎類型的拷貝和淺拷貝一樣。
在拷貝引用類型成員變量時,爲引用類型的數據成員另闢了一個獨立的內存空間,實現真正內容上的拷貝。深拷貝相比於淺拷貝速度較慢並且花銷較大,需要實現 Cloneable 並重寫 clone() 方法。

8.System.gc() 和 Runtime.gc() 會做什麼事情?

java.lang.System.gc()只是java.lang.Runtime.getRuntime().gc()的簡寫,兩者的行爲沒有任何不同。
唯一要能說有什麼不同那就是在字節碼層面上調用前者比調用後者短一點點,前者是1條字節碼而後者是2條。
實際運行起來性能幾乎一樣。不過如果對字節碼大小非常非常敏感的話建議用System.gc()。
從通常的代碼習慣說也是System.gc()用得多些。

9.finalize() 方法什麼時候被調用?析構函數 (finalization) 的目的是什麼?

調用時機:
垃圾回收器(garbage collector)決定回收某對象時,就會運行該對象的finalize()方法;
析構函數 (finalization) 的目的
GC本來就是內存回收了,應用還需要在finalization做什麼呢? 答案是大部分時候,什麼都不用做(也就是不需要重載)。只有在某些很特殊的情況下,比如你調用了一些native的方法(一般是C寫的),可以要在finaliztion裏去調用C的釋放函數。

10.如果對象的引用被置爲 null,垃圾收集器是否會立即釋放對象佔用的內存?

不會,在下一個垃圾回調週期中,這個對象將是被可回收的。
也就是說並不會立即被垃圾收集器立刻回收,而是在下一次垃圾回收時纔會釋放其佔用的內存。

11.什麼是分佈式垃圾回收(DGC)?它是如何工作的?

RMI 子系統實現基於引用計數的“分佈式垃圾回收”(DGC),以便爲遠程服務器對象提供自動內存管理設施。
當客戶機創建(序列化)遠程引用時,會在服務器端 DGC 上調用 dirty()。
當客戶機完成遠程引用後,它會調用對應的 clean() 方法。
針對遠程對象的引用由持有該引用的客戶機租用一段時間。
租期從收到 dirty() 調用開始。
在此類租約到期之前,客戶機必須通過對遠程引用額外調用 dirty() 來更新租約。
如果客戶機不在租約到期前進行續簽,那麼分佈式垃圾收集器會假設客戶機不再引用遠程對象。

12.串行(serial)收集器和吞吐量(throughput)收集器的區別是什麼?

串行GC:
整個掃描和複製過程均採用單線程的方式,相對於吞吐量GC來說簡單;適合於單CPU、客戶端級別。
吞吐量GC:
採用多線程的方式來完成垃圾收集。
適合於吞吐量要求較高的場合,比較適合中等和大規模的應用程序。

13.在 Java 中,對象什麼時候可以被垃圾回收?

當一個對象到GC Roots不可達時,在下一個垃圾回收週期中嘗試回收該對象,如果該對象重寫了finalize()方法,並在這個方法中成功自救(將自身賦予某個引用),那麼這個對象不會被回收。
但如果這個對象沒有重寫finalize()方法或者已經執行過這個方法,也自救失敗,該對象將會被回收。

14.JVM的永久代中會發生垃圾回收麼?

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。
如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。
這就是爲什麼正確的永久代大小對避免Full GC是非常重要的原因。
請參考下Java8:從永久代到元數據區(譯者注:Java8中已經移除了永久代,新加了一個叫做元數據區的native內存區)。

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