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