1.java的內存
java的內存結構分為
- 堆 (是gc的主要區域) 線程共享,主要是用於分配實例對象和數組
- 棧 線程私有,它的生命周期和線程相同,又分成 虛擬機棧和本地方法棧,只有它會報 StackOverFlowError,棧深度超標
- 方法區 線程共享 用於儲存被虛擬機加載的類的信息,靜態變量 常量和編譯后的.class字節碼
- 程序計數器 線程私有,線程之間不相互影響,獨立存取;
以上部分,線程私有是不會發生gc.並且他們是隨線程生隨線程滅,即程序計數器 本地方法棧和虛擬機棧
來張圖更詳細
2.GC回收機制--判斷是否可以gc
- 引用計數算法
原理:通過一個計數器對對象進行計數,對象被引用時+1,引用失效時-1;當計數為0時則說明可以被回收;
缺點:很難解決對象的相互循環引用問題 - 可達性分析算法
Java虛擬機所采用的算法;
原理:通過一些列稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
那么哪些對象可以被稱為gc roots呢----虛擬機棧(棧中的本地變量列表)/方法區靜態屬性/方法區常量引用/本地方法棧中JNI 所引用的的對象都是可以作為 gc roots的
3.GC回收機制--如何回收
- 標記清除算法
清除算法分成2個階段--標記和清除; 標記階段對所有存活的階段進行標記,標記完成后,再掃描整個空間未標記對象,直接回收不存活的對象.
優點:大多數情況下比較高效,缺點是會造成內存碎片,碎片太多導致后面過程中對大內存的分配無足夠空間時而提前猝發一次垃圾回收動作; - 復制算法
將可用內存將容量划分成大小相等的2塊,每次清理時將其中A內存還存活的對象復制到B內存里面,然后再把A中清理掉;
優點高效且並不產生碎片,缺點犧牲了一半的內存為代價
適用存活對象少,回收對象多 - 標記整理算法
該算法標記階段和標記清除算法一樣,完成標記后它不是直接清理可回收對象,而是將存活對象都向一端移動最后清理掉端邊界意外的內存;
適用於存活對象多,回收對象少的情況 - 分代收集算法
整合了復制算法和標記整理算法,根據新生代和老年代的不同特性采取上面的不同算法
新生代 生命周期短,每次回收時都有大量垃圾對象需要回收 復制算法
老年代 每次只有少量的對象需要回收 標記整理算法
深入理解分代回收算法 Survivor(幸存者) Eden (谷歌翻譯為伊甸園)
- 復制算法中內存划分其實並不是按照1:1來划分老年代和新生代,,而是按照8:1:1分一個大的Eden區和兩個小的survivor的空間
- 為什么需要2個Survivor區 新生代一般經歷15次Gc就可以移到老年代.當第一次gc時,我們可以把Eden的存活對象放入Survivor A空間,第二次Gc時,Survivor A也要使用復制算法,存活對象放到Survivor B上,第三次gc時,又將Survivor B對象復制到Survivor A上如此循環往復;
- 為什么Eden這么大,因為新生代中存活的對象,需要轉移的Survivor 的對象不多,算是緩解了復制算法的缺點;
4.GC回收機制--gc的執行機制
- Scavenge GC
當新對象生成並且在Eden申請空間失敗時就會觸發Scavenge GC;Eden區的gc會比較頻繁 - Full GC
是對整個堆進行清理,要比Scavenge GC要慢,什么情況要進行Full GC呢,如下四種:
持久代被寫滿
System.gc調用
老年代被寫滿
上一次GC之后Heap的各域分配策略動態變化
持久代:
用於存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class