基本的垃圾回收算法
引用計數(Reference Counting)
增加一個引用,引用計數加1,去掉一個引用,引用計數減1,然后回收那些引用計數為0的對象
問題:無法處理循環引用問題(例如A、B兩個對象互相引用,但沒有其他對象引用它們,這時它們也無法被回收)
標記-清除(Mark-Sweep)
從引用根節點開始標記所有被引用的對象,然后遍歷整個堆,清除未標記的對象
問題:產生碎片
復制(Copying)
首先將內存空間分為對等的兩半,每次只使用其中一半
每次回收時,遍歷當前使用區域,將正在使用的對象復制到另外一個區域
好處:一次遍歷即可,且不會產生碎片
問題:需要兩倍空間
標記-整理(Mark-Compact)
從引用根節點開始標記所有被引用的對象,然后遍歷整個堆,清除未標記的對象,并把存活對象壓縮到一塊
好處:避免了空間的浪費,且不會產生碎片
比較
空間:復制>標記-清除=標記-整理(復制需要兩倍空間)
時間:復制<標記-清除<標記-整理(復制最快,一次遍歷即可;標記-整理比標記-清除要慢,因為除了清除之外,還要移動數據)
JVM分代結構
JVM內存采用分代結構,分別為Young、Tenured、Permanent,其中Young又細分為Eden和兩個大小相同的Survivor區:From和To。
分代依據
- 絕大部分的對象都是臨時對象
- 不同對象的生命周期不同,采用不同的算法,可以提高不同的效率
JVM GC過程
新建的對象都在Eden中創建
大的對象直接在Old中創建:1)超過-XX:PretenureSizeThreshold設置,2)大于整個Eden
如果Eden滿了,則觸發MinorGCMinorGC
暫停程序
將Eden和From中存活的對象復制到To,同時各個對象的年齡值加1(MinorGC后,Eden和From都是空的)
如果To滿了,則將對象移到Old,如果此時Old滿了,則發送Promotion Failed錯誤,觸發FullGC
如果對象的年齡超過-XX:MaxTenuringThreshold,也移到Old(這里有一個動態對象年齡的概念:不是每次都要求對象的年齡一定要超過-XX:MaxTenuringThreshold才晉升到Old,如果Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入Old)FullGC
如果Old滿了,觸發FullGC
如果Perm滿了,觸發FullGC
暫停程序(CMS算法的整個過程可以并行執行,只需短暫暫停程序2次)
回收Old,如果回收后還是滿了,則拋出OutOfMemoryError: Java heap space
默認情況下,JVM是不回收Perm區的,要回收需要使用CMS算法,并設置-XX:+CMSClassUnloadingEnabled, -XX:+CMSPermGenSweepingEnabled,如果回收后還是滿了,則拋出OutOfMemoryError: PermGen space
JVM GC算法
串行
效率高,但無法利用多核,一般在小程序使用,使用-XX:+UseSerialGC打開
并行
對Young并行收集,使用-XX:+UseParallelGC打開
JDK6.0后可對Old進行并行收集,使用-XX:+UseParallelOldGC打開
并發
保證大部分回收工作并發執行(應用不暫停),適合響應要求高的應用,使用-XX:+UseConcMarkSweepGC打開
G1
待補
比較
Serial | Throughput | CMS | G1 | |
---|---|---|---|---|
參數 | -XX:+UseSerialGC | -XX:+UseParallelGC | -XX:+UseConcMarkSweepGC, -XX:+UseParNewGC | -XX:+UseG1GC |
Young(都是暫停整個應用) | 單線程 | 多線程 | 多線程 | 多線程 |
Old | 單線程,暫停應用,壓縮 | 多線程,暫停應用,壓縮 | 單或多線程,部分暫停,不壓縮 | 多線程,部分暫停,壓縮 |
增加CPU使用率,產生碎片,如果沒有足夠的CPU或者碎片太多,則退化成serial gc | 增加CPU使用率,適合Heap大于4G的情況,Old區也是從一個region拷貝到另外一個region |
G1和CMS的機制是差不多的,只是G1把old分區了,這樣更有利于多線程的掃描
CMS每次清除后,都不會壓縮整理的,會產生碎片,而G1每次都像young那樣,進行數據移動,也就解決了碎片的問題
選擇
- 如果heap少于100MB,選擇Serial
- 對于TPS,如果CPU夠用,則選擇并發GC,如果CPU使用率較高,則選擇Throughput
- 對于平均響應時間,通常Throughput比并發GC要好
- 對于90%或99%的響應時間,并發GC比Throughput要好
- 如果選用并發GC,heap少于4G選擇CMS,大于4G選擇G1(這個保留,對G1算法不了解,了解后再修正)
GC Root
垃圾回收從Root開始,棧是程序真正執行的地方,所以從棧開始找,而棧又屬于線程獨有,所以從所有的線程的棧開始找
- 線程的棧幀中引用的對象
- 方法區中的類靜態屬性引用的對象
- 方法區中的常量引用的對象
- 本地方法棧中JNI引用的對象