Java = (C++)--html
MibBridge *pBridge = new cmBaseGroupBridge () ;
//若是註冊失敗,使用Delete釋放該對象所佔內存區域
if (pBridge->Register(kDestroy)!= NO_ERROR)
delete pBridge;
複製代碼
MibBridge *pBridge = new cmBaseGroupBridge();
pBridge 一> Register(kDestroy);
複製代碼
圖示分析證實java沒有采用引用計數法
java
若是不下當心直接把0bj1 一reference和0bj2 一reference置null。 則在Java堆當中的兩塊內存依然保持着互相引用,沒法回收。git
/**
* -XX:+PrintGCDetails
* 證實:java使用的不是引用計數算法
*/
public class RefCountGC {
//這個成員屬性惟一的做用就是佔用一點內存
private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB
Object reference = null;
public static void main(String[] args) {
RefCountGC obj1 = new RefCountGC();
RefCountGC obj2 = new RefCountGC();
obj1.reference = obj2;
obj2.reference = obj1;
obj1 = null;
obj2 = null;
//顯式的執行垃圾回收行爲
//這裏發生GC,obj1和obj2可否被回收?
System.gc();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
也叫根搜索算法或追蹤性垃圾收集程序員
在Java語言中,GC Roots包括如下幾類元素:github
注意面試
對象是否"死亡"算法
/**
* 測試Object類中finalize()方法,即對象的finalization機制。
*
*/
public class CanReliveObj {
public static CanReliveObj obj;//類變量,屬於 GC Root
//此方法只能被調用一次
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("調用當前類重寫的finalize()方法");
obj = this;//當前待回收的對象在finalize()方法中與引用鏈上的一個對象obj創建了聯繫
}
public static void main(String[] args) {
try {
obj = new CanReliveObj();
// 對象第一次成功拯救本身
obj = null;
System.gc();//調用垃圾回收器
System.out.println("第1次 gc");
// 由於Finalizer線程優先級很低,暫停2秒,以等待它
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
System.out.println("第2次 gc");
// 下面這段代碼與上面的徹底相同,可是此次自救卻失敗了
obj = null;
System.gc();
// 由於Finalizer線程優先級很低,暫停2秒,以等待它
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
控制檯輸出數據庫
第1次 gc
調用當前類重寫的finalize()方法
obj is still alive
第2次 gc
obj is dead
複製代碼
MAT是Memory Analyzer的簡稱,它是一 款功能強大的Java堆內存分析器。用於查找內存泄漏以及查看內存消耗狀況。
MAT是基於Eclipse開發的,是一款免費的性能分析工具。
能夠在http://www.eclipse org/mat/下載並使用MAT。緩存
方式1: 命令行使用jmapbash
方式2:使用JVisualVM導出
public class GCRootsTest {
public static void main(String[] args) {
List<Object> numList = new ArrayList<>();
Date birth = new Date();
for (int i = 0; i < 100; i++) {
numList.add(String.valueOf(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("數據添加完畢,請操做:");
new Scanner(System.in).next();
numList = null;
birth = null;
System.out.println("numList、birth已置空,請操做:");
new Scanner(System.in).next();
System.out.println("結束");
}
}
複製代碼
使用MAT查看GC Roots
使用jProfiler進行GC溯源
使用Jprofiler分析OOM
/**
* -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
*
*/
public class HeapOOM {
byte[] buffer = new byte[1 * 1024 * 1024];//1MB
public static void main(String[] args) {
ArrayList<HeapOOM> list = new ArrayList<>();
int count = 0;
try{
while(true){
list.add(new HeapOOM());
count++;
}
}catch (Throwable e){
System.out.println("count = " + count);
e.printStackTrace();
}
}
}
複製代碼
控制檯輸出
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid45386.hprof ...
Heap dump file created [7390812 bytes in 0.019 secs]
count = 6
java.lang.OutOfMemoryError: Java heap space
at com.dsh.jvm.gc.algorithm.HeapOOM.<init>(HeapOOM.java:12)
at com.dsh.jvm.gc.algorithm.HeapOOM.main(HeapOOM.java:20)
複製代碼
對應count=6
出現OOM的代碼
當成功區分出內存中存活對象和死亡對象後,GC接下來的任務就是執行垃圾回收,釋放掉無用對象所佔用的內存空間,以便有足夠的可用內存空間爲新對象分配內存.
目前在JVM中比較常見的三種垃圾收集算法是標記一清除算法( Mark一Sweep)、複製算法(Copying)、標記一壓縮算法(Mark一Compact)
標記一清除算法(Mark一Sweep)是一種很是基礎和常見的垃圾收集算法,該算法被J . McCarthy等人在1960年提出並並應用於Lisp語言。
當堆中的有效內存空間(available memory) 被耗盡的時候,就會中止整個程序(也被稱爲stop the world),而後進行兩項工做,第一項則是標記,第二項則是清除。
注意:何爲清除?
爲了解決標記一清除算法在垃圾收集效率方面的缺陷,M.L.Minsky於1963年發表了著名的論文,「 使用雙存儲區的Li sp語言垃圾收集器CALISP Garbage Collector Algorithm Using SerialSecondary Storage )」。M.L. Minsky在該論文中描述的算法被人們稱爲複製(Copying)算法,它也被M. L.Minsky本人成功地引入到了Lisp語言的一個實現版本中。
將活着的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時將正在.使用的內存中的存活對象複製到未被使用的內存塊中,以後清除正在使用的內存塊中的全部對象,交換兩個內存的角色,最後完成垃圾回收。
堆中S0和S1使用的就是複製算法
在新生代,對常規應用的垃圾回收,一次一般能夠回收708一 99的內存空間。回收性價比很高。因此如今的商業虛擬機都是用這種收集算法回收新生代。
複製算法的高效性是創建在存活對象少、垃圾對象多的前提下的。這種狀況在新生代常常發生,可是在老年代,更常見的狀況是大部分對象都是存活對象。若是依然使用複製算法,因爲存活對象較多,複製的成本也將很高。所以,基於老年代垃圾回收的特性,須要使用其餘的算法。
標記一清除算法的確能夠應用在老年代中,可是該算法不只執行效率低下,並且在執行完內存回收後還會產生內存碎片,因此JVM的設計者須要在此基礎之上進行改進。==標記一壓縮(Mark一Compact) 算法由此誕生==。
1970年先後,G. L. Steele 、C. J. Chene和D.S. Wise 等研究者發佈標記一壓縮算法。在許多現代的垃圾收集器中,人們都使用了標記一壓縮算法或其改進版本。
第一階段和標記一清除算法同樣,從根節點開始標記全部被引用對象.
第二階段將全部的存活對象壓縮到內存的一端,按順序排放。
以後,清理邊界外全部的空間。
標記一壓縮算法的最終效果等同於標記一清除算法執行完成後,再進行一次內存碎片整理,所以,也能夠把它稱爲標記一清除一壓縮(Mark一 Sweep一Compact)算法。
兩者的本質差別在於標記一清除算法是一種非移動式的回收算法,標記一壓.縮是移動式的。是否移動回收後的存活對象是一項優缺點並存的風險決策。
能夠看到,標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當咱們須要給新對象分配內存時,JVM只須要持有一個內存的起始地址便可,這比維護一個空閒列表顯然少了許多開銷。
若是內存空間以規整和有序的方式分佈,即已用和未用的內存都各自一邊,彼此之間維繫着一個記錄下一次分配起始點的標記指針,當爲新對象分配內存時,只須要經過修改指針的偏移量將新對象分配在第一個空閒內存位置上,這種分配方式就叫作指針碰撞(Bump the Pointer) 。
Mark-Sweep | Mark-Compact | Copying | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空間開銷 | 少(但會堆積碎片) | 少(不堆積碎片) | 一般須要活對象的2倍大小(不堆積碎片) |
移動對象 | 否 | 是 | 是 |
難道就沒有一種最優的算法麼?
==沒有最好的算法,只有更合適的算法
==
前面全部這些算法中,並無一種算法能夠徹底替代其餘算法,它們都具備本身獨特的優點和特色。分代收集算法應運而生。
分代收集算法,是基於這樣一個事實:不一樣的對象的生命週期是不同的。所以,==不一樣生命週期的對象能夠採起不一樣的收集方式,以便提升回收效率==。通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色使用不一樣的回收算法,以提升垃圾回收的效率。
在Java程序運行的過程當中,會產生大量的對象,其中有些對象是與業務信息相關,好比Http請求中的Session對象、線程、Socket鏈接, 這類對象跟業務直接掛鉤,所以生命週期比較長。可是還有一些對象,主要是程序運行過程當中生成的臨時變量,這些對象生命週期會比較短,好比: String對象, 因爲其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次便可回收。
目前幾乎全部的GC都是採用分代收集(Generational Collecting) 算法執行垃圾回收的。 在HotSpot中,基於分代的概念,GC所使用的內存回收算法必須結合年輕代和老年代各自的特色。
年輕代(Young Gen)
老年代(Tenured Gen)
以HotSpot中的CMS回收器爲例,CMS是基於Mark一 Sweep實現的,對於對象的回收效率很高。而對於碎片問題,CMS採用基於Mark一Compact算法的Serial 0ld回收器做爲補償措施:當內存回收不佳(碎片致使的Concurrent Mode Failure時),將採用Serial 0ld執行Full GC以達到對老年代內存的整理。
分代的思想被現有的虛擬機普遍使用。幾乎全部的垃圾回收器都區分新生代和老年代。
上述現有的算法,在垃圾回收過程當中,應用軟件將處於一種stop the World的狀態。在Stop the World狀態下,應用程序全部的線程都會掛起,暫停一切正常的工做,等待垃圾回收的完成。若是垃圾回收時間過長,應用程序會被掛起好久,將嚴重影響用戶體驗或者系統的穩定性。爲了解決這個問題,即對實時垃圾收集算法的研究直接致使了增量收集(Incremental Collecting) 算法的誕生。
若是一次性將全部的垃圾進行處理,須要形成系統長時間的停頓,那麼就可讓垃圾收集線程和應用程序線程交替執行。每次,垃圾收集線程只收集一小片區域的內存空間,接着切換到應用程序線程。依次反覆,直到垃圾收集完成。
總的來講,增量收集算法的基礎還是傳統的標記一清除和複製算法。增量收集算法經過對線程間衝突的妥善處理,容許垃圾收集線程以分階段的方式完成標記、清理或複製工做。
使用這種方式,因爲在垃圾回收過程當中,間斷性地還執行了應用程序代碼,因此能減小系統的停頓時間。可是,由於線程切換和上下文轉換的消耗,會使得垃圾回收的整體成本上升,形成系統吞吐量的降低。
通常來講,在相同條件下,堆空間越大,一次GC時所須要的時間就越長,有關GC產生的停頓也越長。爲了更好地控制GC產生的停頓時間,將一塊 大的內存區域分割成多個小塊,根據目標的停頓時間,每次合理地回收若干個小區間,而不是整個堆空間,從而減小一次GC所產生的停頓。
分代算法將按照對象的生命週期長短劃分紅兩個部分,分區算法將整個堆空間劃分紅連續的不一樣小區間。
每個小區間都獨立使用,獨立回收。這種算法的好處是能夠控制一次回收多少個小區間。
注意,這些只是基本的算法思路,實際GC實現過程要複雜的多,目前還在發展中的前沿GC都是複合算法,而且並行和併發兼備。
【代碼】
github.com/willShuhuan…
【筆記】
JVM_01 簡介
JVM_02 類加載子系統
JVM_03 運行時數據區1- [程序計數器+虛擬機棧+本地方法棧]
JVM_04 本地方法接口
JVM_05 運行時數據區2-堆
JVM_06 運行時數據區3-方法區
JVM_07 運行時數據區4-對象的實例化內存佈局與訪問定位+直接內存
JVM_08 執行引擎(Execution Engine)
JVM_09 字符串常量池StringTable
JVM_10 垃圾回收1-概述+相關算法
JVM_11 垃圾回收2-垃圾回收相關概念
JVM_12 垃圾回收3-垃圾回收器