java自動(dòng)管理內(nèi)存主要解決的兩個(gè)問題: 1、給對(duì)象分配內(nèi)存,上篇博客已經(jīng)介紹過了http://blog.csdn.net/ying1414058425/article/details/60141543 2、回收分配給對(duì)象的內(nèi)存,下面我將一步一步的進(jìn)行分析
垃圾收集GC(Garbage Colletion)主要回收的區(qū)域是java堆和方法區(qū) 因?yàn)槌绦?a >計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧三個(gè)區(qū)域的生命周期是和線程一樣的 他們的內(nèi)存分配和具有確定性,在類結(jié)構(gòu)確定下來就已知的,所以不需要過多的考慮回收問題,因?yàn)榉椒ńY(jié)束或者線程結(jié)束,內(nèi)存自然就回收了 java堆和方法區(qū),只有在運(yùn)行期間才知道會(huì)創(chuàng)建哪些對(duì)象,內(nèi)存分配和回收都是動(dòng)態(tài)的
在堆里放著Java幾乎所有的對(duì)象實(shí)例,垃圾回收器在對(duì)堆進(jìn)行回收前,第一件事情就是確定對(duì)象中有哪些還存活,哪些已經(jīng)死去(即不可能再被任何途徑使用的對(duì)象)
判斷對(duì)象是否存活的算法: 1、引用計(jì)數(shù)算法 給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí)計(jì)數(shù)器值就加一,當(dāng)引用失效時(shí)計(jì)數(shù)器值就減一,任何時(shí)刻計(jì)數(shù)器為0時(shí)的對(duì)象就是不可能再被使用的 計(jì)數(shù)算法的問題: 主流的java虛擬機(jī)沒有選用引用計(jì)數(shù)算法來管理內(nèi)存,主要是因?yàn)樗茈y解決對(duì)象之間相互循環(huán)引用的問題 例如對(duì)象A和對(duì)象B 互相引用著對(duì)方 ,及時(shí)當(dāng)對(duì)象都賦值為null ,因?yàn)樗麄兊囊糜?jì)數(shù)都不為0,所以引用計(jì)數(shù)器無法通知GC收集器來回收他們
2、可達(dá)性分析算法 主流的商用語言(java、c#)的主流實(shí)現(xiàn)中都是通過可達(dá)性分析(Reachability Analysis)來判斷對(duì)象是否存活 算法思路: 通過一系列的成為“GC Roots”的對(duì)象作為起始起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑成為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的
在java語言中,可作為GC Roots的對(duì)象: 虛擬機(jī)棧中(幀棧中本地變量表)中引用的對(duì)象 方法區(qū)中類靜態(tài)屬性應(yīng)用的對(duì)象 方法區(qū)中常量引用的對(duì)象 本地方法棧中JNI(Native方法)中引用的對(duì)象
再說一下引用 強(qiáng)引用: 類似 Object obj = new Object() 只要強(qiáng)引用在 Object就不會(huì)被回收 軟引用:用SoftReference關(guān)鍵字,內(nèi)存溢出之前,會(huì)進(jìn)行回收 弱引用:比軟引用更弱一點(diǎn),用WeekReference關(guān)鍵字,垃圾回收器工作時(shí),無論內(nèi)存是否足夠,都會(huì)回收 虛引用:最弱的引用,用PhantomReference關(guān)鍵字,不會(huì)對(duì)被引用的對(duì)象產(chǎn)生任何影響,無法通過虛引用獲取到對(duì)象實(shí)例,唯一作用是能在對(duì)象被回收是收到一個(gè)系統(tǒng)通知
堆中對(duì)象的回收 一個(gè)對(duì)象被回收,要標(biāo)記兩次 第一次,沒有與GC Roots相連的引用鏈,那么他會(huì)進(jìn)行第二次篩選,看他是否有必要執(zhí)行finalize()方法 如果此對(duì)象沒有覆蓋finalize()方法 ,或者finalize()方法被虛擬機(jī)調(diào)用過了,就不必執(zhí)行 如果有必要執(zhí)行,那么這個(gè)對(duì)象會(huì)放在F-Queue隊(duì)列中 第二次,GC對(duì)F-Queue中的對(duì)象進(jìn)行標(biāo)記,如果在finalize()方法中,對(duì)象有被引用就會(huì)被回收 如果對(duì)象與引用鏈上的對(duì)象建立關(guān)聯(lián),他就會(huì)被移除即將回收的集合 建議:不用使用finalize()方法,對(duì)象沒有在GC Roots的引用鏈中,就會(huì)被回收,不要用finalize進(jìn)行自救
方法區(qū)的回收 方法區(qū)GC主要收集兩部分內(nèi)容:廢棄常量和無用的類 廢棄常量:例如一個(gè)常量沒有任何引用,他就會(huì)被清除里常量池 無用的類:例如一個(gè)類的實(shí)例全部被回收,加載該類的ClassLoader被回收,該類對(duì)用的java.lang.Class對(duì)象沒有在任何地方被引用
1、標(biāo)記 - 清除算法 先將要回收的內(nèi)存標(biāo)記出來然后再清除 不足:效率不高,清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片
2、 復(fù)制算法 將內(nèi)存按容量劃分為大小相等的兩塊,每次使用一塊,當(dāng)一塊內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另一塊上,然后將已使用過的內(nèi)存清理掉 優(yōu)點(diǎn):不會(huì)產(chǎn)生內(nèi)存碎片,實(shí)現(xiàn)簡單,運(yùn)行高效 不足:將內(nèi)存縮小為原來一半,代價(jià)太高,并且如果對(duì)象的存活率較高時(shí)就會(huì)進(jìn)行較多次的復(fù)制,效率會(huì)變低 現(xiàn)在的商業(yè)虛擬機(jī)較多使用這種方法,但是內(nèi)存沒平分,因?yàn)榻^大部分對(duì)象都是會(huì)被回收的,所以比例可以為8:1,8為每次存滿了,清空,然后再復(fù)用,1為存活的對(duì)象暫時(shí)儲(chǔ)存的地方,如果存活的對(duì)象多余1的空間,可以依賴其他內(nèi)存進(jìn)行分配擔(dān)保
3、標(biāo)記-整理算法 比標(biāo)記 - 清除算法多了一步整理,就是先標(biāo)記清除,然后存活的對(duì)象整理到一起,避免了內(nèi)存碎片的問題
4、分代收集算法 根據(jù)對(duì)象存活周期的不同,將內(nèi)存劃分為幾塊,一般把java堆分為新生代和老生代 在新生代,對(duì)象存活率低,采用復(fù)制算法 在老生代,對(duì)象存活率高,使用標(biāo)記清理或標(biāo)記整理算法
1、枚舉根節(jié)點(diǎn) GC Roots節(jié)主要是:全局引用(例如常量或類靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表) 枚舉根節(jié)點(diǎn),不允許對(duì)象引用關(guān)系發(fā)生變化,不然準(zhǔn)確性無法保證,這導(dǎo)致GC進(jìn)行時(shí)必須停頓所有java線程 很多應(yīng)用的方法區(qū)中引用太多,要逐個(gè)檢查的話,會(huì)消耗很多時(shí)間 在HotSpot中會(huì)通過OopMap記錄下來數(shù)據(jù)類型,JIT也會(huì)記錄下來哪些是引用,使得GC在在掃描時(shí)就可以知道這些信息
2、安全點(diǎn) 只有在安全點(diǎn),才能停頓下來GC 安全點(diǎn)的選定標(biāo)準(zhǔn)是,程序長時(shí)間的執(zhí)行,例如方法調(diào)用、循環(huán)、異常等 GC時(shí),怎么樣讓所有的線程都跑到安全點(diǎn)再停頓下來,有兩種方法 第一種 搶先式中斷: GC時(shí),中斷所有線程,如果有線程不在安全點(diǎn)上,就恢復(fù)線程,讓他執(zhí)行到安全點(diǎn)上,再中斷他(幾乎不用這種方法) 第二種 主動(dòng)式中斷: 設(shè)置輪詢標(biāo)志,標(biāo)志點(diǎn)為安全點(diǎn)和創(chuàng)建對(duì)象分配內(nèi)存的地方,各個(gè)線程會(huì)主動(dòng)輪詢這個(gè)標(biāo)志,發(fā)現(xiàn)標(biāo)志為真時(shí)就中斷掛起
3、安全區(qū)域 安全點(diǎn)SafePoint不足:當(dāng)程序處于Sleep或Bloked狀態(tài),就不會(huì)分配cup,就無法響應(yīng)JVM的中斷請(qǐng)求 安全區(qū)域Safe Region:一段代碼之中,引用關(guān)系不會(huì)發(fā)生變化,在此區(qū)域的任何地方GC都是安全的 GC時(shí)不用管進(jìn)入安全區(qū)域的線程,線程會(huì)在離開安全區(qū)域前檢查自己是否要GC或者根節(jié)點(diǎn)枚舉,如果沒完成,必要要等待安全離開的命令后才可以離開
4、垃圾收集器 下面簡單介紹幾個(gè)收集器
Serial收集器 Serial收集器是單線程的,GC時(shí)必須暫停其他的所有工作線程,對(duì)于“Stop The World”的不良體驗(yàn),VM的設(shè)計(jì)者說“你媽媽給你打掃房間時(shí),肯定會(huì)讓你老老實(shí)實(shí)的帶著,如果她一邊打掃,你一邊扔紙屑,這房間還能打掃完?”
ParNew收集器 是Serial收集器的多線程版本
Parallel Scavenge 收集器 他是一個(gè)新生代收、使用了復(fù)制算法、并行多線程的收集器 他的設(shè)計(jì)目標(biāo)是,達(dá)到一個(gè)可控的吞吐量 (吞吐量 = 運(yùn)行代碼的時(shí)間 / (運(yùn)行代碼的時(shí)間 +垃圾回收的時(shí)間)) 他提供了兩個(gè)參數(shù)用于精確控制吞吐量:控制最大垃圾停頓時(shí)間 、直接設(shè)置吞吐量大小
Serial Old 收集器 他是Serial收集器的老年代版本,使用標(biāo)記整理算法
Parallel Ord 收集器 他是Parallel Scavenge 收集器的老年代版本,使用多線程和標(biāo)記整理算法
CMS收集器 他的設(shè)計(jì)目標(biāo)是,GC時(shí)間最短 CMS是基于標(biāo)記清除算法,但是更復(fù)雜一點(diǎn),他分為4步
初始標(biāo)記:標(biāo)記GC Roots 直接關(guān)聯(lián)的對(duì)象,速度很快,需要Stop The World 并發(fā)標(biāo)記:進(jìn)行GC Roots 重新標(biāo)記:修改并發(fā)標(biāo)記期間因程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象,需要Stop The World 并發(fā)清除
優(yōu)點(diǎn):響應(yīng)速度快,用戶體驗(yàn)好,因?yàn)楹臅r(shí)最長的并發(fā)標(biāo)記和并發(fā)清除過程中,收集器的線程可以和用戶線程一起工作,所以他的GC停頓時(shí)間還是很短的 不足:并發(fā)階段,GC會(huì)占用一部分CPU資源,導(dǎo)致引用程序變慢,總吞吐量降低 CMS無法處理浮動(dòng)垃圾可能導(dǎo)致另一次Full GC,浮動(dòng)垃圾為出現(xiàn)在標(biāo)記過程之后的垃圾 CMS采用標(biāo)記清除算法,會(huì)產(chǎn)生過多的碎片。可以通過設(shè)置參數(shù),在FullGC時(shí)之前進(jìn)行碎片整理,或者幾次FullGC后進(jìn)行一次整理
G1 收集器 面向服務(wù)端應(yīng)用的垃圾收集器,他的使命是替換掉HotSopt JDK1.5中的CMS 特點(diǎn): 并發(fā)與并行:充分利用多CPU、多核環(huán)境的硬件優(yōu)勢縮短GC停頓時(shí)間 分代收集:分為新生代和老生代 空間整合:整體基于標(biāo)記整理算法,局部使用復(fù)制算法 可預(yù)測停頓:可明確指定在長度為M的時(shí)間片段里,停頓的時(shí)間不超過N毫秒
G1回收步驟 初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收
例如:
33.125 : [ GC DefNew : 3324K->152k( 3712K ) , 0.0025925 secs] 3324K-> 125K(11904K),0.00316 secs33.125: GC發(fā)生時(shí)間,值為JVM啟動(dòng)的秒數(shù) GC : GC停頓的類型, 可以為GC或Full GC DefNew:GC發(fā)成的區(qū)域, 名稱由收集器決定 3324K->152k( 3712K ): (使用內(nèi)存)->GC后使用內(nèi)存(總內(nèi)存) 0.0025925 secs :GC時(shí)間 3324K-> 125K(11904K):(Java堆使用容量)->GC后Java堆使用容量(Java堆總?cè)萘? 0.00316 secs : GC時(shí)間
1、對(duì)象優(yōu)先在Eden分配 大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配,當(dāng)Eden區(qū)中沒有足夠的空間時(shí),JVM就會(huì)發(fā)起一次Minor GC 新生代GC(Minor GC) :發(fā)生在新生代的GC,因?yàn)镴ava對(duì)象大多都是朝生夕滅,所以Minor GC非常頻繁,一半回收速度也較快 老生代GC(Major GC / Full GC) : 發(fā)生在老生代的GC,出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨至少一次的Minor GC,Major GC比Minor GC速度慢10倍以上
2、大對(duì)象直接進(jìn)入老年代 大對(duì)象:需要大量連續(xù)內(nèi)存空間的對(duì)象 JVM提供了一個(gè)參數(shù),使大于這個(gè)設(shè)置的對(duì)象直接在老年代分配,避免使用復(fù)制算法時(shí),產(chǎn)生大量的內(nèi)存復(fù)制
3、長期存活的對(duì)象將進(jìn)入老年代 JVM采用了分代收集的思想來管理內(nèi)存,他會(huì)給每個(gè)對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注