1. 如何判斷對象可以回收?
1.1 引用計數(shù)法
算法:在對象中添加一個引用計數(shù)器,每當有一個地方引用它時罢杉,計數(shù)器值加一;引用失效時贡歧,計數(shù)器值減一滩租。計數(shù)器為0 的對象時不被使用的。
缺點:無法回收兩個相互引用的對象
1.2 可達性分析算法
- java虛擬機中的垃圾回收器采用可達性分析算法來探索所有存活的對象
- 掃描堆中的對象利朵,看是否能夠沿著 GC Root 對象為起點的引用鏈找到該對象律想,找不到,表示可以回收
- 哪些對象可以作為 GC Root 绍弟?
可作為 GC Root 對象:
- 在虛擬機棧(棧中的本地變量表)中引用的對象技即。各個線程被調(diào)用的方法棧中用到的參數(shù)、局部變量等
- 在方法區(qū)中類靜態(tài)屬性引用的對象
- 在方法區(qū)中常量的引用對象樟遣,例如 字符串常量池(StringTable)里的引用
- 在本地方法棧中JNI引用的對象
- Java虛擬機內(nèi)部的引用而叼,例如Class對象,系統(tǒng)類加載器等
- 所有被同步鎖(synchronized 關鍵字)持有的對象
1.3 四種引用
1.強引用
- 只有所有的 GC Root 都不引用該對象豹悬,該對象才能被回收
2.軟引用
- 僅有軟引用引用該對象時葵陵,在垃圾回收后,內(nèi)存仍不足時會再次觸發(fā)垃圾回收瞻佛,回收軟引用對象
- 可以配合引用隊列來釋放軟引用自身 (如果軟引用所引用的對象被垃圾回收脱篙,JAVA虛擬機就會把這個軟引用加入到與之關聯(lián)的引用隊列 ReferenceQueue 中)
public class SoftReferenceTest {
public static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();
//引用隊列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
//關聯(lián)軟引用和引用隊列 當軟引用關聯(lián)的byte[] 被回收時,軟引用自身被加入到引用隊列中
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
list.add(ref);
}
}
}
3.弱引用
- 僅有弱引用引用該對象時伤柄,在垃圾回收時绊困,無論內(nèi)存是否足夠,都會回收弱引用對象
- 可以配合引用隊列 ReferenceQueue 來釋放弱引用自身
虛引用 和 終結器引用必須配合引用隊列
4.虛引用
- 必須配合引用隊列使用适刀,主要配合 ByteBuffer 使用秤朗。被引用對象回收時,會將虛引用入隊蔗彤,由ReferenceHandler 線程調(diào)用虛引用相關方法釋放直接內(nèi)存(虛引用對象cleaner 實際上調(diào)用Unsafe.freeMemory() 川梅, 來釋放直接內(nèi)存)
5.終結器引用
- 無需手動編碼。但其內(nèi)部配合引用隊列使用然遏,在垃圾回收時贫途,終結器引用入隊(被引用對象暫時沒有被回收),再由 Finalizer線程(優(yōu)先級很低)查看引用隊列中的終結器引用待侵,找到被引用對象并調(diào)用它的 finalize() 方法丢早。第二次 GC時,才能回收被引用對象。
java 編程時不要復寫finalize()方法來釋放內(nèi)存怨酝,因為效率很低
2. 垃圾回收算法
2.1 標記-清除 Mark-Sweep
算法:首先標記出所有需要回收的對象傀缩,標記完成后,統(tǒng)一回收掉所有被標記的對象农猬。
或者赡艰,標記出所有存活的對象。清除掉未標記的對象斤葱。
優(yōu)點:
- 速度比較高
- 只需要把需要清除的內(nèi)存起始地址和結束地址記錄到一個表中慷垮。
缺點:
- 執(zhí)行效率不穩(wěn)定
- 如果堆中包含大量對象,大部分都是需要被回收的揍堕,這時必須進行大量標記和清除動作料身,導致標記和清除過程的執(zhí)行效率都隨著對象增長而降低。
- 會造成內(nèi)存碎片衩茸∏垩可能造成以后需要 分配較大對象時無法找到足夠的連續(xù)內(nèi)存而提前觸發(fā)一次垃圾收集
2.2 標記-整理 Mark-Compack
算法:標記需要回收的對象,讓所有存活的對象都向內(nèi)存空間的一端移動楞慈,然后直接清理掉邊界以外的內(nèi)存幔烛。
缺點:
- 速度慢
- 移動存活對象并更新所有引用這些對象的地方將會是一種極為負重的操作
優(yōu)點:
- 沒有內(nèi)存碎片
2.3 標記-復制 Mark-Copy
算法:將可用內(nèi)存按容量劃分大小相等的兩塊,每次只使用其中一塊抖部。當一塊內(nèi)存用完了说贝,就將存活的對象復制到另一塊上面,然后再把已使用的內(nèi)存空間一次清理掉慎颗。
現(xiàn)在的商用java虛擬機大多都優(yōu)先采用了這種收集算法去回收新生代乡恕。
新生代中的對象有98%熬不過第一輪收集,因此并不需要按照1:1的比例來劃分新生代的內(nèi)存空間俯萎。
優(yōu)點:
- 不會有內(nèi)存碎片
缺點:
- 需要占用雙倍內(nèi)存空間
-
將可用的空間縮小為原來的一半傲宜,空間浪費大
復制.png
-
3. 分代垃圾回收
特點:
對象優(yōu)先分配在伊甸區(qū)
大對象直接晉升老年代(新生代空間不足,老年代空間足夠夫啊,直接放到老年代函卒,不會引起Minor GC)
新生代空間不足時,首次觸發(fā) Minor GC, 伊甸區(qū)和 FROM 存活的對象會復制到 TO 中撇眯。存活的對象年齡加1报嵌,并且交換 FROM 和 TO.
Minor GC會引發(fā) stop the world. 暫停其他用戶線程,等待垃圾回收完成熊榛,用戶線程才恢復運行锚国。
當對象年齡超過閾值時,會晉升至老年代(最大壽命是15)
當老年代空間不足玄坦,
會先嘗試觸發(fā) Minor GC
, 如果之后空間仍不足血筑,那么觸發(fā)Full GC
, 那么 stop the world 的時間更長绘沉。
3.1 相關 VM 參數(shù)
含義 | 參數(shù) |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size) |
幸存區(qū)比例(動態(tài)) | -XX:InitialSurvivorRatio=ratio + -XX:UserAdaptiveSizePolicy |
幸存區(qū)比例 | -XX:SurvivorRatio=ratio |
晉升閾值 | -XX:MaxTenuringThreshold=threshold |
晉升詳情 | -XX:+PrintTenuringDistribution |
GC詳情 | -XX:+PrintGCDetails -verbose:gc |
Full GC 前 Minor GC | -XX:+ScavengeBeforeFullGC |
示例:
//-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose gc
public static void main(String[] args) {
}
堆信息
Heap
def new generation total 9216K, used 1147K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 14% used [0x00000000fec00000, 0x00000000fed1edf0, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 2871K, capacity 4480K, committed 4480K, reserved 1056768K
class space used 317K, capacity 384K, committed 384K, reserved 1048576K
3.2 小結
堆內(nèi)存空間分為較大的 Eden 和 兩塊較小的 Survivor 區(qū)。每次只使用 Eden 和 survivor 中的一塊豺总。
這種情況下標記-復制
算法 減少了內(nèi)存空間的浪費车伞。
復制算法-現(xiàn)作為主流的YGC算法進行新生代垃圾回收
4. 垃圾回收器
垃圾回收器分類:
1.串行
- 單線程
- 適用堆內(nèi)存較小,個人電腦
2.吞吐量優(yōu)先
- 多線程
- 堆內(nèi)存較大喻喳,需要多核 CPU 支持
- 單位時間內(nèi)另玖,stop the world的時間最短 (垃圾回收時間占總時間占比低)
3.響應時間優(yōu)先
- 多線程
- 堆內(nèi)存較大,需要多核 CPU 支持
- 垃圾回收時沸枯,(單次的)盡可能讓stop the world時間最短
4.1 串行
開啟串行垃圾回收器:
-XX:+UseSerialGC=Serial + SerialOld
- Serial:工作在新生代日矫,
采用復制算法
- SerialOld:工作在老年代赂弓,
采用標記-整理算法
為什么工作線程要在安全點停下來绑榴?
因為垃圾回收過程中對象的地址可能發(fā)生改變,為了保證安全的使用這些對象地址
4.2 吞吐量優(yōu)先
開啟吞吐量優(yōu)先開關
- -XX:+UseParallelGC 工作在新生代
復制算法
- -XX:+UseParallelOldGC 工作在老年代
標記-整理算法
與之有關的一些參數(shù):
-XX:+UseAdaptiveSizePolicy
- 采用自適應大小調(diào)整 盈魁,動態(tài)調(diào)整新生代eden 和 survivor 的大小 翔怎,包括晉升年齡等
-XX:GCTimeRatio=ratio
- 用來調(diào)整垃圾回收時間和總時間占比 ratio默認值99 1/(1+ratio)
-XX:MaxGCPauseMillis=ms
- 最大暫停毫秒數(shù) 默認200ms
上面兩個設置是沖突的
-XX:ParallelGCTheads=n
- 設置可以開啟的垃圾回收線程
垃圾回收線程跟CPU核數(shù)相關 ,也可以人為設置最大線程數(shù)-XX:ParallelGCTheads=n
4.3 響應時間優(yōu)先
開啟響應時間優(yōu)先的開關:CMS
-XX:+UseConcMarkSweepGC~SerialOld -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC 工作在老年代 標記-清除算法 并發(fā):垃圾回收線程工作的同時用戶線程也能工作杨耙。有可能并發(fā)失敵嗵住(由于內(nèi)存碎片過多),導致退化到 SerialOld垃圾回收器
-XX:+UseParNewGC 工作在新生代 復制算法
與之有關的一些參數(shù):
-XX:ParallelGCTheads=n -XX:ConcGCThread=threads
- 這兩個參數(shù)珊膜,影響初始標記
-XX:CMSInitiatingOccupancyFraction=percent
- 控制何時來進行CMS垃圾回收時機(例如老年代內(nèi)存占用達到80% 觸發(fā)一次內(nèi)存回收)
-XX:+CMSScavengeBeforeRemark
- 重新標記之前對新生代做一次垃圾回收
初始標記時(找到那些跟對象)容握,stop the world
并發(fā)標記:從根對象出發(fā),順著引用鏈標記其他對象
重新標記時车柠,stop the world
4.4 G1 Garbage First
取代了之前的CMS垃圾回收器
適用場景
- 同時注重吞吐量(Throughput)和低延遲(Low Latency),默認暫停目標是 200ms
- 超大堆內(nèi)存剔氏,會將堆劃分為多個大小相等的Region
- 整體上是標記-整理算法,兩個區(qū)域之間是復制算法
相關JVM參數(shù)
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=ms
4.4.1 G1 垃圾回收階段
這是一個循環(huán)的過程
4.4.2 Young Collection
劃分成一個個大小相等的區(qū)域竹祷,每個區(qū)域都可以獨立作為 Eden 谈跛、幸存區(qū)、老年代
- 會stop the world
經(jīng)歷一次young collection 伊甸中幸存的對象拷貝到survivor區(qū)
幸存區(qū)內(nèi)存不足時拷貝到老年代
4.4.3 Young Collection + CM
在young GC 時會進行 GC Root 的初始標記
-
老年代占用堆空間比例達到閾值時塑陵,進行并發(fā)標記(不會STW)立砸,由下面的JVM 參數(shù)決定:
-XX:InitiatingHeapOccupancyPercent=percent (默認 45%)
4.4.4 Mixed Collection
會對E S O 進行全面垃圾回收
- 最終標記(Remark)(標記并發(fā)標記漏掉的對象)伏穆,會STW
- 拷貝存活(Evacuation),會STW (對老年代來說,并不會回收所有區(qū)域盖腿。會回收垃圾最多的老年區(qū))
-XX:MaxGCPauseMillis:ms
4.4.5 Full GC
- Serial GC
- 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
- 老年代內(nèi)存不足發(fā)生的垃圾收集 Full GC
- Parallel GC
- 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
- 老年代內(nèi)存不足發(fā)生的垃圾收集 Full GC
- CMS
- 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
- 老年代內(nèi)存不足----當垃圾回收的速度跟不上垃圾產(chǎn)生的速度會并發(fā)失敗退化為 SerialGC收集器 Full GC
- G1
- 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
- 老年代內(nèi)存不足(首先:Mixed Collection)----當垃圾回收的速度跟不上垃圾產(chǎn)生的速度會并發(fā)失敗退化為 SerialGC收集器 Full GC
4.4.6 Young Collection 跨代引用
減少GC Root 的標記時間
- 卡表與 Remembered Set
- 在引用變量時通過post-writer barrier + dirty card queue
- concurrent refinement threads 更新 Remembered Set
4.4.7 Remark
重新標記階段
4.4.8 JDK 8u20 字符串去重
- 優(yōu)點:節(jié)省大量內(nèi)存
- 缺點:略微多占用了 CPU 時間, 新生代回收時間略微增加
-XX:+UseStringDeduplication
String s1 = new String("hello");
String s2 = new String("hello");
- 將所有新分配的字符串放入一個隊列
- 當新生代回收時旨涝,G1并發(fā)檢查是否有字符串重復
- 如果它們值一樣鱼的,讓他們引用char[]
- 注意,與String.intern() 不一樣
- String.intern() 關注的是字符串對象
- 而字符串去重關注的是char[]
- 在JVM內(nèi)部俯抖,使用了不同的字符串表
4.4.9 JDK 8u40 并發(fā)標記類卸載
所有對象都經(jīng)過并發(fā)標記后输瓜,就知道哪些類不再被使用。當一個類加載器(針對自定義的類加載器)的所有類都不再使用,則卸載它所加載的所有類尤揣。
-XX:+ClassUnloadingWithConcurrentMark 默認開啟
4.4.10 JDK 8u60 回收巨型對象
- 一個對象大于region 的一半時稱為巨型對象
- G1 不會對巨型對象進行拷貝
- 回收時被優(yōu)先考慮
- G1會跟蹤老年代所有的incoming 引用搔啊,這樣老年代 incoming 為 0的巨型對象就可以在新生代回收時處理掉
4.4.11 JDK9 并發(fā)標記起始時間調(diào)整
- 并發(fā)標記必須在堆空間占滿前完成,否則退化為Full GC
- JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent=percent