1.什么是垃圾回收?如何觸發垃圾回收??
垃圾回收(GC)是自動管理內存的一種機制,它負責自動釋放不再被程序引用的對象所占用的內存,這種機制減少內存泄漏和內存管理錯誤的可能性。可以通過多種方式觸發:
- 內存不足時:當JVM檢測到堆內存不足,無法為新的對象分配內存時,會自動觸發垃圾回收。
- 手動請求:雖然垃圾回收是自動的,開發者可以通過調用System.gc()或Runtime.getRuntime().gc()建議JVM進行垃圾回收。不過這只是一個建議,并不能保證立即執行。
- JVM參數:啟動java程序時可以通過JVM參數來調整垃圾回收的行為,比如:-Xmx(最大堆大小)、-Xms(初始堆大小)等。
- 對象數量或內存使用達到閾值:垃圾收集器內部實現了一些策略,以監控對象的創建和內存使用,達到某個閾值時觸發垃圾回收。
2.垃圾判斷方法
- 引用計數法
原理:為每個對象分配一個引用計數器,每當有一個地方引用它時,計數器加1;當引用失效時,計數器減1。當計數器為0時,表示對象不再被任何變量引用,可以被回收。
缺點:不能解決循環引用的問題,即兩個對象相互引用,但不再被其他任何對象引用,這時引用計數器不會為0,導致對象無法被回收。- 可達性分析法
原理:從一組稱為GC Roots(垃圾收集根)的對象出發,向下追溯它們引用的對象,以及
這些對象引用的其他對象,以此類推。如果一個對象到GC Roots沒有任何引用鏈相連(即從GC Roots到這個對象不可達),那么這個對象就被認為是不可達的,可以被回收。
GCRoos對象包括:虛擬機棧中引用的對象、方法區中類靜態屬性引用的對象、本地方法棧中JNl引用的對象、活躍線程的引用等。
例:class A {B b; } class B {C c; } class C {無任何引用 } public class GCTest {public static void main(String[] args) {A a = new A();a.b = new B();a.b.c = new C();// 在此時,a, b, c 這些對象都被 GC Roots 引用,暫時不會被回收。} }//若a=null,那么a和gc root引用斷開,那么a、b和c就無法到達gc root,不可達,會被回收。
3. 垃圾回收算法
- 標記-清除算法
標記清除算法分為“標記”和"清除”兩個階段,首先通過可達性分析,標記出所有需要回收的對象,然后統一回收所有被標記的對象。標記清除算法有兩個缺陷,一是標記和清除的過程效率都不高,二是清除結束后會造成大量的碎片空間。有可能會造成在申請大塊內存的時候因為沒有足夠的連續空間導致再次GC。- 復制算法
為了解決碎片空間的問題,出現了“復制算法”。復制算法的原理是,將內存分成兩塊,每次申請內存時都使用其中的一塊,當內存不夠時,將這一塊內存中所有存活的復制到另一塊上。然后將然后再把已使用的內存整個清理掉。復制算法解決了空間碎片的問題。但是也帶來了新的問題。因為每次在申請內存時只能使用一半的內存空間,內存利用率嚴重不足。- 標記-整理算法
復制算法在GC之后存活對象較少的情況下效率比較高,但如果存活對象比較多時,會執行較多的復制操作,效率就會下降。而老年代的對象在GC之后的存活率就比較高,所以就有人提出了“標記整理算法”。標記整理算法的“標記”過程與”標記-清除算法”的標記過程一致,但標記之后不會直接清理。而是將所有存活對象都移動到內存的一端,移動結束后直接清理掉剩余部分。- 分代回收算法
分代收集是將內存劃分成新生代和老年代。分配的依據是對象的生存周期或者說經歷過的GC次數。對象創建時,一般在新生代申請內存,當經歷一次GC之后如果對還存活,那么對象的年齡+1。當年齡超過一定值(默認是15,可以通過參數XX:MaxTenuringThreshold來設定)后,如果對象還存活,那么該對象會進入老年代。
3. 垃圾回收器
5. 垃圾回收機制
5.1 Minor GC
Minor GC主要負責回收年輕代的垃圾對象。年輕代通常分為三個區域:一個伊甸區和兩個幸存區(一般稱為From區和To區)。當對象被創建時,先被分配到伊甸區。在年輕代的垃圾回收過程中,先對伊甸區進行垃圾回收,將存活的對象復制到一個空閑的幸存區中(通常是From區),同時清空伊甸區。Minor GC會一直重復這樣的過程,直到From區被填滿,則進入To區,To區被填滿之后,則將所有對象移動到老年代中。?
經歷 16 次 Minor GC 還能在新生代中存活的對象,才會被送到老年代,或者Survivor 空間中某個年齡段的對象總大小超過了 Survivor 空間的一半,那么該年齡段及以上年齡段的所有對象都會在下一次垃圾回收時被晉升到老年代。
- 作用范圍:針對年輕代進行回收,包括Eden區和兩個Survivor區(S0和S1)。
- 觸發條件:當Eden區空間不足時,JVM觸發一次Young?GC,將Eden區和一個Survivor區中的存活對象移動到另一個Survivor區或老年代。
- 特點:通常發生的非常頻繁,因為年輕代中對象的生命周期較短,回收效率高,暫停時間相對較短。
5.2?Major GC
Major GC是指對老年代進行的垃圾回收操作。在Java堆內存中,老年代用于存放生命周期較長的對象或者經過多次Minor GC后仍然存活的對象。Major GC的執行時間一般比Minor GC更長,因為它需要處理較多的對象和進行更復雜的內存整理操作。在Major GC期間,應用程序的執行將會暫停,直到垃圾回收操作完成。
- 作用范圍:針對老年代進行回收,但不一定只回收老年代。
- 觸發條件:當老年代空間不足時,或者系統檢測到年輕代對象晉升到老年代的速度過快,可能會觸發Major GC。
- 特點:相比Minor GC,Major GC發生的頻率較低,但每次回收可能需要更長的時間,因為老年代中的對象存活率較高。
5.3?Full GC
當新生代空間不足時,會觸發Minor GC,只清理新生代內存。而老年代空間不足或者為了整理碎片化的內存,會觸發Full GC,對整個堆內存進行回收。
Full GC 可能會導致較長的停頓時間,因為它需要掃描整個堆內存,標記可回收對象,并進行內存整理。這意味著在 Full GC 過程中,應用程序的執行會被暫停。
Full GC 的頻率會受多種因素影響,如堆內存的大小、JVM配置參數、對象分配速度等。如果 Full GC 發生過于頻繁或耗時過長,可能會導致應用程序的性能下降。
- 作用范圍:對整個堆內存(包括年輕代、老年代以及永久代/元空間)進行回收。
- 觸發條件:
1.?直接調用System.gc()或Runtime.getRuntime().gc()方法時,雖然不能保證立即執行,但JVM會嘗試執行Full GC。
2. Minor GC時,如果存活的對象無法全部放入老年代,或者老年代空間不足以容納存活的對象,則會觸發FuGC,對整個堆內存進行回收。
3. 當永久代(Java8之前的版本)或元空間(Java8及以后的版本)空間不足時。- 特點:Full GC是最昂貴的操作,因為它需要停止所有的工作線程(Stop The World),遍歷整個堆內存來查找和回收不再使用的對象,因此應盡量減少Full GC的觸發。