Java虛擬機器--物件回收
從這篇開始我們開始探討一些jvm調優的問題。在jvm調優中一個離不開的重點是垃圾回收,當垃圾回收成為系統達到更高併發量的瓶頸時,我們就需要對jvm中如果進行“自動化”垃圾回收技術實施必要的監控和調節。
對於調優之前,我們必須要了解其執行原理,java 的垃圾收集Garbage Collection 通常被稱為“GC”,它誕生於1960年 MIT 的 Lisp 語言,經過半個多世紀,目前已經十分成熟了。因此本篇主要從這三個方面來了解:
1. 哪些物件需要被回收?
2. 什麼時候回收?
3. 如何回收?
一、誰要被回收
java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同是資料區域,這些區域有各自各自的用途。主要包含以下幾個部分組成:
1、程式計數器
程式計數器佔用的記憶體空間我們可以忽略不計,它是每個執行緒所執行的位元組碼的行號指示器。
2、虛擬機器棧
java的虛擬機器棧是執行緒私有的,生命週期和執行緒相同。它描述的是方法執行的記憶體模型。同時用於儲存區域性變數、運算元棧、動態連結、方法出口等。
3、本地方法棧
本地方法棧,類似虛擬機器棧,它呼叫的是是native方法。
4、堆
堆是jvm中管理記憶體中最大一塊。它是被共享,存放物件例項。也被稱為“gc堆”。垃圾回收的主要管理區域
5、方法區
方法區也是共享的記憶體區域。它主要儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器(jit)編譯後的程式碼資料。
以上就是jvm在執行時期主要的記憶體組成,我們看到常見的記憶體使用不但存在於堆中,還會存在於其他區域,雖然堆的管理對程式的管理至關重要,但我們不能只侷限於這一個區域,特別是當出現記憶體洩露的時候,我們除了要排查堆記憶體的情況,還得考慮虛擬機器棧的以及方法區域的情況。
知道了要對誰以及那些區域進行記憶體管理,我還需要知道什麼時候對這些區域進行垃圾回收。
二、什麼時候回收
在垃圾回收之前,我們必須確定的一件事就是物件是否存活?這就牽扯到了判斷物件是否存活的演算法了。
引用計數演算法:
給物件中新增一個引用計數器,每當有一個地方引用它時,計數器+1,當引用失效,計數器-1.任何時刻計數器為0的物件就是不可能再被使用的。
優點:實現簡單,判定效率高效,被actionscript3和python中廣泛應用。
缺點:無法解決物件之間的相互引用問題。java沒有采納
可達性分析演算法:
通過一系列稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GCRoots沒有任何引用鏈相連的時候,則證明此物件是不可用的。
比如如下,右側的物件是到GCRoot時不可達的,可以判定為可回收物件。
在java中,可以作為GCRoot的物件包括以下幾種:
* 虛擬機器棧中引用的物件。
* 方法區中靜態屬性引用的物件。
* 方法區中常量引用的物件。
* 本地方法中JNI引用的物件。
基於以上,我們可以知道,噹噹前物件到GCRoot中不可達時候,即會滿足被垃圾回收的可能。
那麼是不是這些物件就非死不可,也不一定,此時只能宣判它們存在於一種“緩刑”的階段,要真正的宣告一個物件死亡。至少要經歷兩次標記:
第一次:物件可達性分析之後,發現沒有與GCRoots相連線,此時會被第一次標記並篩選。
第二次:物件沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機器呼叫過,此時會被認定為沒必要執行。
三、如何回收
上述的兩點講解之後,我們大概明白了,哪些物件會被回收,以及回收的依據是什麼,但回收的這個工作實現起來並不簡單,首先它需要掃描所有的物件,鑑別誰能夠被回收,其次在掃描期間需要 ”stop the world“ 物件能被凍結,不然你剛掃描,他的引用資訊有變化,你就等於白做了。
分代回收
我們從一個object1來說明其在分代垃圾回收演算法中的回收軌跡。
1、object1新建,出生於新生代的Eden區域。
2、minor GC,object1 還存活,移動到Fromsuvivor空間,此時還在新生代。
3、minor GC,object1 仍然存活,此時會通過複製演算法,將object1移動到ToSuv區域,此時object1的年齡age+1。
4、minor GC,object1 仍然存活,此時survivor中和object1同齡的物件並沒有達到survivor的一半,所以此時通過複製演算法,將fromSuv和Tosuv 區域進行互換,存活的物件被移動到了Tosuv。
5、minor GC,object1 仍然存活,此時survivor中和object1同齡的物件已經達到survivor的一半以上(toSuv的區域已經滿了),object1被移動到了老年代區域。
6、object1存活一段時間後,發現此時object1不可達GcRoots,而且此時老年代空間比率已經超過了閾值,觸發了majorGC(也可以認為是fullGC,但具體需要垃圾收集器來聯絡),此時object1被回收了。fullGC會觸發 stop the world。
在以上的新生代中,我們有提到物件的age,物件存活於survivor狀態下,不會立即晉升為老生代物件,以避免給老生代造成過大的影響,它們必須要滿足以下條件才可以晉升:
1、minor gc 之後,存活於survivor 區域的物件的age會+1,當超過(預設)15的時候,轉移到老年代。
2、動態物件,如果survivor空間中相同年齡所有的物件大小的綜合和大於survivor空間的一半,年級大於或等於該年級的物件就可以直接進入老年代。
以上採用分代垃圾收集的思想,對一個物件從存活到死亡所經歷的歷程。期間,在新生代的時刻,會用到複製演算法,在老年代時,有可能會用到標記-清楚演算法(mark-sweep)演算法或者標記-整理演算法,這些都是垃圾回收演算法基於不同區域的實現,我們看下這幾種回收演算法的實現原理。
垃圾回收演算法
標記清除法(Mark-Sweep)
標記清除法是垃圾回收演算法的思想基礎。標記清除演算法將垃圾分為兩個階段:標記階段和清除階段。
標記階段,通過根節點,標記所有從根節點開始的可達物件,未標記過的物件就是未被引用的垃圾物件。
清除階段,清除所有未被標記的物件。
複製演算法(Copying)
複製演算法是,將原有的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在適用的記憶體中存活物件複製到未使用的記憶體塊,然後清除使用的記憶體塊中所有的物件。
標記壓縮演算法(Mark-Compact)
標記壓縮演算法是一種老年代的回收演算法。
標記階段和標記清除演算法一致,對可達物件做一次標記。
清理階段,為了避免記憶體碎片產生,將所有的存活物件壓縮到記憶體的一端。
四、垃圾收集器
垃圾收集器是記憶體回收的具體實現,不同的廠商提供的垃圾收集器有很大的差別,一般的垃圾收集器都會作用於不同的分代,需要搭配使用。以下是各種垃圾收集器的組合方式:
各種組合的優缺點:
新生代GC策略 | 年老代GC策略 | 說明 | |
組合1 | Serial | Serial Old | Serial和Serial Old都是單執行緒進行GC,特點就是GC時暫停所有應用執行緒。 |
組合2 | Serial | CMS+Serial Old | CMS(Concurrent Mark Sweep)是併發GC,實現GC執行緒和應用執行緒併發工作,不需要暫停所有應用執行緒。另外,當CMS進行GC失敗時,會自動使用Serial Old策略進行GC。 |
組合3 | ParNew | CMS | 使用-XX:+UseParNewGC選項來開啟。ParNew是Serial的並行版本,可以指定GC執行緒數,預設GC執行緒數為CPU的數量。可以使用-XX:ParallelGCThreads選項指定GC的執行緒數。 如果指定了選項-XX:+UseConcMarkSweepGC選項,則新生代預設使用ParNew GC策略。 |
組合4 | ParNew | Serial Old | 使用-XX:+UseParNewGC選項來開啟。新生代使用ParNew GC策略,年老代預設使用Serial Old GC策略。 |
組合5 | Parallel Scavenge | Serial Old | Parallel Scavenge策略主要是關注一個可控的吞吐量:應用程式執行時間 / (應用程式執行時間 + GC時間),可見這會使得CPU的利用率儘可能的高,適用於後臺持久執行的應用程式,而不適用於互動較多的應用程式。 |
組合6 | Parallel Scavenge | Parallel Old | Parallel Old是Serial Old的並行版本 |
組合7 | G1GC | G1GC | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #開啟 -XX:MaxGCPauseMillis =50 #暫停時間目標 -XX:GCPauseIntervalMillis =200 #暫停間隔目標 -XX:+G1YoungGenSize=512m #年輕代大小 -XX:SurvivorRatio=6 #倖存區比例 |
轉載:https://my.oschina.net/u/1859679/blog/1548866