垃圾回收
垃圾回收:
JVM中如何將對象視爲垃圾??
1.那些內存需要回收?(對象是否可以被回收的兩種經典算法: 引用計數法 和 可達性分析算法)
2.什麼時候回收? (堆的新生代、老年代、永久代的垃圾回收時機,MinorGC 和 FullGC)
3.如何回收?(三種經典垃圾回收算法(標記清除算法、複製算法、標記整理算法)及分代收集算法 和 七種垃圾收集器)
在JVM提供垃圾回收機制,將內存空間不再使用的對象進行回收,
垃圾回收主要針對堆空間,垃圾回收操作需要消耗一定的資源和時間。
-
堆空間分區:
JVM中對堆空間進行分區:年輕代、老年代、永久代(1.8中無);
垃圾回收有兩種類型:Minor GC 和 Full GC。
(1)Minor GC:對新生代進行回收,不會影響到年老代。因爲新生代的 Java 對象大多死亡頻繁,所以 Minor GC 非常頻繁,一般在這裏使用速度快、效率高的算法,使垃圾回收能儘快完成。
(2)Full GC:對年輕代和老年代同時作用的GC稱之爲Full GC;也稱Major GC,對整個堆進行回收,包括新生代和老年代。由於Full GC需要對整個堆進行回收,所以比Minor GC要慢,因此應該儘可能減少Full GC的次數,導致Full GC的原因包括:老年代被寫滿、永久代(Perm)被寫滿和System.gc()被顯式調用等。
java中4中引用:強引用、軟引用、弱引用、虛引用;
a. **強引用:一個對象如果只有強引用,那麼垃圾回收器絕不會回收它;**
new String();//強引用
即使在內存不足的情況下,JVM寧願拋出內存不足的異常都不會回收它;
String s = new String(“”);
s = null;//會被回收
b. **軟引用:如果一個對象只有軟引用,如果內存充足的情況下,垃圾回收器不會回收它;如果內存不足的情況下,垃圾回收器纔會回收該對象;**
java中提供SoftReference處理軟引用;
c. **弱引用:弱引用所作用的對象,一旦發生垃圾回收,該引用所作用的對象會被立馬回收掉;**
弱引用和軟引用的對象比較:相對弱引用的生命力更強
java中提供WeakReference來處理弱引用;
d. **虛引用:虛引用無法左右當前對象的生命週期;**
java中提供PhantomReference來通知當前對象被回收掉;
a、b、c、d生命週期越來越長;
-
JVM如何識別java對象是垃圾??
-
引用計數法:
對對象添加引用標識,每對對象增加一個引用,引用標識+1,減少引用標識-1,當標識爲0時,說明對象不存在引用,可以被垃圾回收掉;
引用計數無法處理相互引用的問題;
class A{ private B _b; } class B{ private A _a; } class Test{ public add(){ A a = new A(); //a引用計數爲1 B b = new B(); //b引用計數爲1 a._b = b; //b的引用計數爲2 b._a = a; //a的引用計數爲2 } } 當退出add()方法,局部變量a、b都失效
-
可達性分析:(根搜索法)
判斷對象是否存活將堆中對象想象成一個樹,從樹根(Root)開始遍歷所有的對象,能夠達到的對象稱之爲可用對象(存活對象),不可達的對象稱之爲垃圾;
樹根:GC Roots 一定可達;
java中哪一些對象一定是可達的呢???或者是java中哪一些可以作爲GC的Roots呢
(1)虛擬機棧中的對象;
(2)本地方法棧中JNI(Native 方法)引用的對象;
(3)方法區中靜態屬性引用的對象;
(4)方法去中常量引用的對象;
-
垃圾回收算法
- 標記-清除(Mark-Sweep):
“標記-清除”算法是最基礎的算法;
分爲“標記”和“清除”兩個階段:
標記階段:jvm會掃描所有的對象實例,通過根搜索算法,將活躍對象進行標記(對象是否可達,分析對象之間的引用關係,可達性分析);
清除階段:jvm再一次掃描所有對象,將未標記的對象(不可達)進行清除,只有清除動作,不作任何的處理,這樣導致的結果會存在很多的內存碎片。
- 它主要由兩個缺點:
(1)效率問題,標記和清除過程的效率低,即標記階段和清除階段都要遍歷一次整個堆內存空間;
(2)空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。內存碎片化,即對象連續的內存空間;
- 複製算法(Copying):
複製算法中,會將內存劃分爲兩塊相等大小的內存區域A/B,然後生成的數據會存放在A區,當A區剩餘空間不足以存放下一個新創建的對象時,系統就會將A區中的有效對象全部複製到B區中,而且是連續存放的。然後直接清空A區中的所有對象。
解決了標記-清除缺點,但仍存在問題。
- 缺點:
(1)效率問題:將內存空間分爲兩大塊,對內存空間只使用了一半,內存空間使用效率低;
(2)拷貝效率低;不適合用來生命週期比較長的對象;朝生夕滅(年輕代)使用此算法;
- 標記-整理算法:
標記-壓縮算法是一種老年代的回收算法,它在標記-清除算法的基礎上做了一些優化。
首先也需要從根節點開始對所有可達對象做一次標記,但之後,它並不簡單地清理未標記的對象,而是將所有的存活對象壓縮到內存的一端。之後,清理邊界外所有的空間。這種方法既避免了碎片的產生,又不需要兩塊相同的內存空間,因此,其性價比比較高。
- 分代回收算法:
分代收集算法就是目前虛擬機使用的回收算法,它解決了標記整理不適用於老年代的問題,將內存分爲各個年代。一般情況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),在堆區之外還有一個代就是永久代(Permanet Generation)。
在不同年代使用不同的算法,從而使用最合適的算法,新生代存活率低,可以使用複製算法。而老年代對象存活率搞,沒有額外空間對它進行分配擔保,所以只能使用標記清除或者標記整理算法。
內存擔保策略 --> 老年代給的(Tenured)
垃圾回收過程
年輕代分爲Eden區和survivor區(兩塊兒:from和to),且Eden:from:to==8:1:1。
jvm內存結構
1)新產生的對象優先分配在Eden區(除非配置了-XX:PretenureSizeThreshold,大於該值的對象會直接進入年老代);
2)當Eden區滿了或放不下了,Eden區進行一次Minor GC,清空Eden區,這時候會將Eden區中存活的對象會複製到from區。
3)之後產生的對象繼續分配在Eden區,當Eden區又滿了或放不下了,這時候將會把Eden區和from區存活下來的對象複製到to區(同理,如果存活下來的對象to區都放不下,則這些存活下來的對象全部進入年老代),之後回收掉Eden區和from區的所有內存。
4)如上這樣,會有很多對象會被複制很多次(每複製一次,對象的年齡就+1),默認情況下,當對象被複制了15次(這個次數可以通過:-XX:MaxTenuringThreshold來配置),就會進入年老代了。
5)當年老代滿了或者存放不下將要進入年老代的存活對象的時候,就會發生一次Full GC(這個是我們最需要減少的,因爲耗時很嚴重)。