基本的垃圾回收算法
引用計數(shù)(Reference Counting)
增加一個引用,引用計數(shù)加1,去掉一個引用,引用計數(shù)減1,然后回收那些引用計數(shù)為0的對象
問題:無法處理循環(huán)引用問題(例如A、B兩個對象互相引用,但沒有其他對象引用它們,這時它們也無法被回收)
標記-清除(Mark-Sweep)
從引用根節(jié)點開始標記所有被引用的對象,然后遍歷整個堆,清除未標記的對象
問題:產生碎片

復制(Copying)
首先將內存空間分為對等的兩半,每次只使用其中一半
每次回收時,遍歷當前使用區(qū)域,將正在使用的對象復制到另外一個區(qū)域
好處:一次遍歷即可,且不會產生碎片
問題:需要兩倍空間

標記-整理(Mark-Compact)
從引用根節(jié)點開始標記所有被引用的對象,然后遍歷整個堆,清除未標記的對象,并把存活對象壓縮到一塊
好處:避免了空間的浪費,且不會產生碎片

比較
空間:復制>標記-清除=標記-整理(復制需要兩倍空間)
時間:復制<標記-清除<標記-整理(復制最快,一次遍歷即可;標記-整理比標記-清除要慢,因為除了清除之外,還要移動數(shù)據(jù))
JVM分代結構
JVM內存采用分代結構,分別為Young、Tenured、Permanent,其中Young又細分為Eden和兩個大小相同的Survivor區(qū):From和To。

分代依據(jù)
- 絕大部分的對象都是臨時對象
- 不同對象的生命周期不同,采用不同的算法,可以提高不同的效率
JVM GC過程

新建的對象都在Eden中創(chuàng)建
大的對象直接在Old中創(chuàng)建:1)超過-XX:PretenureSizeThreshold設置,2)大于整個Eden
如果Eden滿了,則觸發(fā)MinorGCMinorGC
暫停程序
將Eden和From中存活的對象復制到To,同時各個對象的年齡值加1(MinorGC后,Eden和From都是空的)
如果To滿了,則將對象移到Old,如果此時Old滿了,則發(fā)送Promotion Failed錯誤,觸發(fā)FullGC
如果對象的年齡超過-XX:MaxTenuringThreshold,也移到Old(這里有一個動態(tài)對象年齡的概念:不是每次都要求對象的年齡一定要超過-XX:MaxTenuringThreshold才晉升到Old,如果Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入Old)FullGC
如果Old滿了,觸發(fā)FullGC
如果Perm滿了,觸發(fā)FullGC
暫停程序(CMS算法的整個過程可以并行執(zhí)行,只需短暫暫停程序2次)
回收Old,如果回收后還是滿了,則拋出OutOfMemoryError: Java heap space
默認情況下,JVM是不回收Perm區(qū)的,要回收需要使用CMS算法,并設置-XX:+CMSClassUnloadingEnabled, -XX:+CMSPermGenSweepingEnabled,如果回收后還是滿了,則拋出OutOfMemoryError: PermGen space
JVM GC算法
串行
效率高,但無法利用多核,一般在小程序使用,使用-XX:+UseSerialGC打開

并行
對Young并行收集,使用-XX:+UseParallelGC打開
JDK6.0后可對Old進行并行收集,使用-XX:+UseParallelOldGC打開

并發(fā)
保證大部分回收工作并發(fā)執(zhí)行(應用不暫停),適合響應要求高的應用,使用-XX:+UseConcMarkSweepGC打開

G1
待補
比較
| Serial | Throughput | CMS | G1 | |
|---|---|---|---|---|
| 參數(shù) | -XX:+UseSerialGC | -XX:+UseParallelGC | -XX:+UseConcMarkSweepGC, -XX:+UseParNewGC | -XX:+UseG1GC |
| Young(都是暫停整個應用) | 單線程 | 多線程 | 多線程 | 多線程 |
| Old | 單線程,暫停應用,壓縮 | 多線程,暫停應用,壓縮 | 單或多線程,部分暫停,不壓縮 | 多線程,部分暫停,壓縮 |
| 增加CPU使用率,產生碎片,如果沒有足夠的CPU或者碎片太多,則退化成serial gc | 增加CPU使用率,適合Heap大于4G的情況,Old區(qū)也是從一個region拷貝到另外一個region |
G1和CMS的機制是差不多的,只是G1把old分區(qū)了,這樣更有利于多線程的掃描
CMS每次清除后,都不會壓縮整理的,會產生碎片,而G1每次都像young那樣,進行數(shù)據(jù)移動,也就解決了碎片的問題
選擇
- 如果heap少于100MB,選擇Serial
- 對于TPS,如果CPU夠用,則選擇并發(fā)GC,如果CPU使用率較高,則選擇Throughput
- 對于平均響應時間,通常Throughput比并發(fā)GC要好
- 對于90%或99%的響應時間,并發(fā)GC比Throughput要好
- 如果選用并發(fā)GC,heap少于4G選擇CMS,大于4G選擇G1(這個保留,對G1算法不了解,了解后再修正)
GC Root
垃圾回收從Root開始,棧是程序真正執(zhí)行的地方,所以從棧開始找,而棧又屬于線程獨有,所以從所有的線程的棧開始找

- 線程的棧幀中引用的對象
- 方法區(qū)中的類靜態(tài)屬性引用的對象
- 方法區(qū)中的常量引用的對象
- 本地方法棧中JNI引用的對象