標(biāo)簽(空格分隔): JVM
概述
- GC需要完成的事情
- 哪些內(nèi)存需要進(jìn)行回收?
- 什么時候回收?
- 怎么回收?
- GC關(guān)注的部分
- Java堆
- 方法區(qū)
- 解釋:一個接口中的多個實(shí)現(xiàn)類所需要的內(nèi)存可能不一樣,一個方法中的多個分支所需的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期間才知道會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收都是動態(tài)的
對象的存在
判定對象存活的算法
- 引用計數(shù)算法(Reference Counting)
- 給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器就加1;當(dāng)引用失效時,計數(shù)器就減1;任何時刻計數(shù)器為0的對象都是不可能再被使用的
- 但是:主流Java虛擬機(jī)里面沒有選用引用計數(shù)算法來管理內(nèi)存,因為它沒有辦法解決對象之間相互循環(huán)引用的問題
- 可達(dá)性分析算法(Reachability Analysis)
- 基本思路:通過一系列成為“GC Roots” 的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑成為引用鏈,當(dāng)一個對象到GC Roots 沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots 到這個對象不可達(dá))時,則證明此對象是不可用的
- 可作為GC Roots 的對象
- 虛擬機(jī)棧(局部變量表)中引用的對象
- 方法區(qū)中靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(一般來說的Native方法)引用的對象
對象引用
- 四種對象引用概述
- 強(qiáng)引用(Strong Reference)
- 軟引用(Soft Reference)
- 弱引用(Weak Reference)
- 虛引用(Phantom Reference)
- 四種引用強(qiáng)度依次減弱
- 四種引用對象詳解
- 強(qiáng)引用:就是指程序代碼中普遍存在的,類似“Object obj=new Object()”這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象
- 軟引用:用來描述一些還有用但并非必需的對象;對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存的話,才會拋出內(nèi)存溢出異常
- 弱引用:也是用來描述非必需對象的,但是比軟引用的作用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象
- 虛引用:也成為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象的實(shí)例,唯一目的就是能在這個對象被收集器回收時受到一個系統(tǒng)通知
對象的生存或死亡
- 判斷一個對象真正死亡要至少經(jīng)過兩次標(biāo)記
- 第一次標(biāo)記:如果對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會被第一次標(biāo)記并且進(jìn)行第一次篩選
- 篩選判定的條件是此對象有沒有必要執(zhí)行finalize方法,當(dāng)對象沒有覆蓋此方法或者此方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為沒有必要執(zhí)行
- 如果這個對象被判定有必要執(zhí)行finalize方法,那么這個對象將會放置在一個叫做F-Queue的隊列之中,并由一個有虛擬機(jī)自動建立的低優(yōu)先級的Finalizer線程去執(zhí)行它
- 第二次標(biāo)記:finalize方法是對象逃脫死亡命運(yùn)的最后一次機(jī)會稍后GC將會對F-Queue中的對象進(jìn)行第二次小規(guī)模的標(biāo)記,如果對象要在finalize方法中成功拯救自己--只要重新與引用鏈上的任何一個對象建立聯(lián)系即可,譬如把自己(this關(guān)鍵字)賦值給某個變量或者對象的成員變量,那在第二次標(biāo)記時,它將會被移除“即將回收”的集合
- 第一次標(biāo)記:如果對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會被第一次標(biāo)記并且進(jìn)行第一次篩選
回收方法區(qū)
- 永久代的垃圾收集
- 廢棄常量:
- 無用的類:
- 該類的所有實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實(shí)例
- 加載該類的ClassLoader已經(jīng)被回收
- 該類對應(yīng)的Java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
垃圾收集算法
- 整體概述
- 標(biāo)記-清除算法(Mark-Sweep)
- 復(fù)制算法(Copying)
- 標(biāo)記整理算法(Mark-Compact)
- 分代收集算法(Generational Collection)
- 模塊詳解
-
標(biāo)記-清除算法:首先標(biāo)記要回收的對象,在標(biāo)記完成之后統(tǒng)一回收所有被標(biāo)記的對象
- 效率問題:
- 空間問題:會產(chǎn)生大量的內(nèi)存碎片
-
復(fù)制算法:將內(nèi)存劃分為大小相等的兩塊,每次只使用其中的一塊,當(dāng)這一塊內(nèi)存用完了,就將還存活的對象復(fù)制到另一塊上去,然后再把已經(jīng)使用過的內(nèi)存空間一次清理掉
- 商用虛擬機(jī)實(shí)現(xiàn):IBM公司的專門研究表明,新生代中的對象98%都是朝生夕死的,所以并不需要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中一塊Survivor;當(dāng)回收時,將Eden空間和剛才用過的Survivor中還存活著的對象一次性的復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間
- HotSpot:默認(rèn)Eden空間和Survivo空間的大小比例8:1,也就是說,每次新生代中可用內(nèi)存空間為整個新生代容量的90%,只有10%會被浪費(fèi);當(dāng)Survivor空間不夠時,需要依賴其他內(nèi)存(老年代)進(jìn)行分配擔(dān)保(Handle Promotion)
- 標(biāo)記-整理算法:根據(jù)老年代的特點(diǎn),先標(biāo)記要清楚的對象,然后將所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存
-
分代收集算法:把Java堆分為新生代和老年代
- 新生代:每次垃圾收集都發(fā)現(xiàn)會有大批對象死去,只有少量存活,那就選用復(fù)制算法
- 老年代:對象存活率較高、沒有額外的空間對它進(jìn)行分配擔(dān)保,就必須使用”標(biāo)記-清除“或”標(biāo)記-整理“算法
-
標(biāo)記-清除算法:首先標(biāo)記要回收的對象,在標(biāo)記完成之后統(tǒng)一回收所有被標(biāo)記的對象
HotSpot的算法實(shí)現(xiàn)
枚舉根節(jié)點(diǎn)
在HotSpot虛擬機(jī)的實(shí)現(xiàn)中,虛擬機(jī)有辦法達(dá)到當(dāng)執(zhí)行系統(tǒng)停頓下來(即Stop-The-World)并不需要一個不漏的檢查完所有執(zhí)行上下文和全局的引用位置,而直接得到哪些地方存放著對象的引用的目的,即使用一組OopMap的數(shù)據(jù)結(jié)構(gòu)來達(dá)到這個目的,在類加載完成的時候,HotSpot就把對象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計算出來,在JIT編譯過程中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用
安全點(diǎn)
- 概述
- 實(shí)際上,HotSpot也的確沒有為每一條指令都生成OopMap,前面已經(jīng)提到,只是在“特定的位置”記錄了這些信息,這些位置成為安全點(diǎn)(Safepoint)
- 方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等具有讓程序長時間執(zhí)行的特征的指令才會產(chǎn)生Safepoint
- 在GC發(fā)生時,讓所有線程跑到安全點(diǎn)
- 搶先式中斷(Preemptive):
- 主動式中斷(Voluntary Suspension):輪詢標(biāo)志
安全區(qū)域
安全區(qū)域(Safe Region)是指在一段代碼之中,引用關(guān)系不會發(fā)生變化;在這個區(qū)域內(nèi)的任何地方開始GC都是安全的
垃圾收集器

整體概述
- 各種收集器
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
- G1收集器
模塊詳解
-
Serial收集器
- 單線程
- “Stop-The-World”
- Client模式下簡單而高效,新生代收集器
-
ParNew收集器
- 多線程
- “Stop-The-World”
- Server模式下,首選新生代收集器
-
Parallel Scavenge收集器
- 并行多線程
- 新生代收集器
- 復(fù)制算法
- 目的:達(dá)到一個可控的吞吐量(吞吐量=運(yùn)行用戶代碼時間/(運(yùn)行用戶代碼時間+垃圾收集時間))
-
Serial Old收集器
- 單線程
- 標(biāo)記整理算法
- 老年代收集器
- 主要作用是在Client模式下
-
Parallel Old收集器
- 多線程
- 標(biāo)記整理算法
- 老年代收集器
-
CMS收集器(Concurrent Mark Sweep)
- 主要目的:獲取最短回收停頓時間
- 標(biāo)記清除算法
- 運(yùn)作過程
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 重新標(biāo)記
- 并發(fā)清除
- 缺點(diǎn)
- 對CPU資源敏感
- 無法處理浮動垃圾
- 大量空間碎片產(chǎn)生
-
G1收集器(Garbage-First)
- 并行和并發(fā)
- 分代收集
- 空間整合
- 可預(yù)測的停頓
- 運(yùn)作過程
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 最終標(biāo)記
- 篩選回收






GC日志的理解

- 各部分詳解
- 第一塊(紅色):表示GC發(fā)生的時間,從Java虛擬機(jī)啟動以來經(jīng)過的秒數(shù)
- 第二塊(紫色):表示GC的停頓類型,如果是Full,則這次GC是發(fā)生了Stop-The-World的
- 第三塊(青色):表示GC發(fā)生的區(qū)域,名稱與GC收集器相關(guān)聯(lián)
- 第四塊(綠色):表示GC前該內(nèi)存區(qū)域已使用容量->GC后該內(nèi)存區(qū)域已使用容量(該內(nèi)存區(qū)域總?cè)萘浚?/li>
- 第五塊(橙色):表示GC所占用的時間,單位為秒
- 第六塊(黃色):表示GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆總?cè)萘浚?/li>
內(nèi)存分配與回收策略
對象優(yōu)先在Eden(新生代)分配
- 當(dāng)Eden區(qū)域沒有足夠的空間進(jìn)行分配時,虛擬機(jī)將發(fā)起一次Minor GC
- Minor GC和Major GC
- Minor GC(新生代GC):非常頻繁,而且速度較快
- Major GC(老年代GC):一般比Minor GC慢上10倍以上
大對象直接進(jìn)入老年代
大對象:需要大量連續(xù)的內(nèi)存空間的Java對象,例如很長的字符串以及數(shù)組
長期存活的對象將進(jìn)入老年代
虛擬機(jī)給每個對象定義了一個對象年齡計數(shù)器,如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor 容納的話,將被移動到Survivor空間中,并且對象年齡設(shè)為1,對象在Survivor區(qū)中每熬過一次Minor GC年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)是15歲),就會被晉升到老年代
動態(tài)年齡判定
如果在Survivor空間中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,而無須等到MaxTenuringThreshold中要求的年齡
空間分配擔(dān)保
在發(fā)生Minor GC之前,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代的所有對象空間,如果這個條件成立,那么Minor GC可以確保是安全的,如果不成立,則虛擬機(jī)會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會繼續(xù)查找老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險的:如果小于,或者HandlePromotionFailure設(shè)置不允許冒險,那這時也要改為進(jìn)行一次Full GC