您好,登錄后才能下訂單哦!
這篇文章主要講解了“java垃圾收集器與內存分配策略是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java垃圾收集器與內存分配策略是什么”吧!
java虛擬機一個老生常談的問題就是垃圾回收和內存分配。java虛擬機內存的自動化管理最終歸結為自動的解決兩個問題:給對象分配內存,以及回收分配給對象的內存。
先說回收分配給對象的內存吧,其中最重要也就輸入無用對象的收集了,其實也就兩步走,收集-釋放。收集有涉及到垃圾收集算法,下面具體講一下。
判定對象是否需要被回收的算法
垃圾收集算法的核心當然是判定當前對象是否已無用,然后才開始收集。《深入理解java虛擬機》中將了兩種收集算法。一種為引用計數算法,一種為可達性分析算法。
一.引用計數算法:
給對象中添加一個程序計數器,對象被調用一次,計數器加1,當對象的引用失效時,計數器減1.當計數器為0 時則表示對象可能已經無用需要被回收。
拓展兩點:
1.當對象被new出來的時候,如果沒有使用它,是會被回收的,也就是說創建初始時,計數器為0。
2.當對象引用失效時,計數器減1。我記得當時被面試問到對象引用怎么失效的,怎么判斷。現在想想當使用失效應該不是我們控制的,當對應被引用之后,過一段時間,這個引用就會自動失效吧。(這里是個人理解,有錯誤望指正!)
雖然引用計數算法很簡單明了,但是有一個嚴重的問題就是循環引用的對象無法被回收。類似下面這種:
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
所以出現了另一種垃圾收集的算法。
二.可達性分析算法:
算法的基本思想是,通過一系列的“GC Roots ”的對象作為起點,通過這些起點能找到的對象則說明該對象是存活的,如果通過所有的起點都不能達到該對象,則說明該對象可能是無用的,會被回收。
主流的虛擬機都是通過可達性分析算法來判定對象是否要被回收的。
下面給大家看一個例子證明java虛擬機是通過可達性分析算法來判斷的。
package chapter.three;
//Debug Configuration:
//-verbose:gc: -Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC()
{
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
System.out.println("第一次");
objA.instance = objB;
objB.instance = objA;
System.out.println(objA);
System.out.println(objB);
objA = null;
objB = null;
System.gc();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二次");
objA.instance = objB;
objB.instance = objA;
System.out.println(objA);
System.out.println(objB);
}
public static void main(String[] args)
{
testGC();
}
}
objA 和objB是相互引用的,如果是采用計數器的算法,兩次輸出應該是一樣的,如果是采用可達性分析的算法,第二次會報錯,因為兩個對象被回收了,這樣操作兩個對象會報錯找不到。運行結果。
第一次chapter.three.ReferenceCountingGC@15db9742chapter.three.ReferenceCountingGC@6d06d69c第二次Exception in thread "main" java.lang.NullPointerException at chapter.three.ReferenceCountingGC.testGC(ReferenceCountingGC.java:34) at chapter.three.ReferenceCountingGC.main(ReferenceCountingGC.java:43)
拓展講一點吧,其實這個不是很重要,了解一下
另外書中還講了一點,哪些不可達的對象,也不一定就會被回收,并沒有直接判死刑,而是判的緩刑,有一次自救的機會。也就是說,所有的“GC Roots”根節點都無法到該對象時,該對象會被標記一次,并對其進行一次篩選。篩選的條件是改對象有沒有重寫finalize()方法,如果沒有重寫,那么該對象就會被判死刑,會被回收。如果該對象有重寫finalize()方法,就會執行這個方法,對象可以在這個方法中自救。但是這個方法只會條用一次,也就是說第二次如果還是不可達還是會被回收掉。不知道大家有沒有理解,下面看一個例子
package chapter.three;
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK=null;
public void isAlive(){
System.out.println("yes,i am still alive...");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed !");
FinalizeEscapeGC.SAVE_HOOK=this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK=new FinalizeEscapeGC();
System.out.println("first:");
SAVE_HOOK=null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK !=null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead...");
}
System.out.println("second:");
SAVE_HOOK=null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK !=null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead...");
}
}
}
結果
first:
finalize method executed !
yes,i am still alive...
second:
no,i am dead...
但是書中建議我們最好不要這么做,因為finalize()能做的,try-finally語句或者其他的語句就能做,,且做的更好,所以我這也是簡單的提一下。
垃圾收集算法
有4種,標記-清除算法,復制算法,標記整理算法,分代收集算法。其實也就三種,以為分代收集算就是不同場景使用前面的三種方法。
標記清除算法 :就是先將需要回收的內存標記,然后清除掉。這種算法思路簡單,但是會造成內存分布零散,存儲大文件會導致觸發一次GC.
復制算法:是將內存按照容量劃分成大小相等的兩塊,每次都只使用其中的一塊,其中一塊內存使用完了,就會統計出這塊內存中存活的對象復制到另一塊內存中,并且清理當前快,這樣循環使用。但是這樣的缺點就是導師內存使用率太低,總有一半的內存在閑置狀態。
標記整理的算法,標記整理算法也是先將需要回收的內存進行整理,然后并不是直接清理掉,而是將存活的對象向前移動,保存內存的前面都是存活的對象,然后清理掉回收的對象。這樣做的解決了標記清除算法導致的內存可用空間分布的太散的問題,但是這樣做的缺點就是效率低下。
分代收集算法:是把java堆分為老年代,新生代。新生代中對象存活時間普遍短使用復制的算法,老年代對象周期較長,采用標記整理算法或者標記清除算法。
垃圾收集器
上面的算法部分都是回收的理論,現在垃圾收集器則是內存回收的具體實現啦,虛擬機中存在很多的收集器,并且這些收集器是配合使用的,各種收集器有各自的優缺點,下圖是HotSpot虛擬機中使用的垃圾收集器,以及這些收集器之間哪些是可以搭配只用的。
serial收集器:是一個單線程收集器,只有一個cpu或者一條收集線程去進行收集,而且更重要的是在收集線程在進行垃圾收集的過程中會暫停用所有的用戶線程,導致用戶進行等待。
ParNew收集器:ParNew收集器是serial收集器的多線程版本,但是一樣的,在收集線程工作時,所有的用戶線程會進行等待。
parallel Scavenge收集器 是一個新生代收集器,使用的復制算法,是并行的多線程。這個收集器關注點是吞吐量,吞吐量是指,虛擬機總共運行100s,垃圾收集占用1s,那么吞吐量就是99%。
se'rial Old 收集器 是serial收集器的老年代版本,采用的是標記整理的算法。一樣是一個單線程收集器。
parallel Old 收集器是parallel Scavenge收集器的老年代版本,采用的是標記整理的算法,多線程。
GMS收集器:是一種獲取最短回收停頓時間為目標的收集器。是基于標記清除 的算法實現的。有4個過程:
初始標記
并發標記
重新標記
并發清除
其中 初始標記和重新標記兩部依然會“stop the world”也就是說這兩部還是會造成用戶線程等待,但是相對而言等待的時間要短很多。
G1收集器,是一種面向服務器的收集器,具備以下特點:
并行與并發
分代收集
空間整合
可預測的停頓
G1收集器大致分為以下幾個步驟
初始標記
并發標記
最終標記
篩選回收
內存分配與回收策略
對象優先在eden(新生代)中分配。
大對象直接進入老年代
長期存活的對象進入老年代
動態對象年齡判定
感謝各位的閱讀,以上就是“java垃圾收集器與內存分配策略是什么”的內容了,經過本文的學習后,相信大家對java垃圾收集器與內存分配策略是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。