垃圾收集器在對堆進行回收前,第一件事情就是要肯定這些對象之中哪些還「活着」,哪些已經"死去」,即不能再被任何途徑使用的對象。php
給對象加一個引用計數器,每當有一個地方引用它時,計數器值加1;當引用失效的時候,計數器值減1;任什麼時候刻計數器爲0的對象就是不可能再被使用的。html
引用計數法的實現簡單,判斷效率也很高,可是主流的java虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的緣由是它很難解決對象之間的循環引用的問題。java
舉個例子:算法
對象objA和對象objB都有字段instance,賦值令objA.instance = objB 以及objB.instance = objA,除此以外,這兩個對象再無任何引用,可是他們互相引用着對方,致使它們的引用計數都不爲0,因而引用計數算法沒法通知GC收集器回收他們。緩存
在主流的商用程序語言中的主流實現中,都是經過可達性分析來斷定對象是否存活的。jvm
這個算法的基本思想是經過一系列稱爲「GC Roots」的對象做爲起始點,若是從GC roots到這個對象不可達,即一個對象到GC Roots 沒有任何引用鏈相連,則證實此對象是不可用的。post
在java語言中,可做爲GC Roots 的對象包括如下幾種:性能
java中將引用分爲強引用、軟引用、弱引用、虛引用4種,這4種引用強度依次逐漸減弱。學習
強引用:ui
強引用是指程序代碼之中廣泛存在的,相似"Object obj = new Object()"這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
軟引用:
軟引用是用來描述一些還有用但非必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收。
弱引用:
被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。
虛引用:
爲一個對象設置虛引用的目的是能在這個對象被垃圾收集機制回收時收到一個系統通知。
圖解:綠色是被標記爲可回收的,當回收後,未使用的內存空間很是零碎,產生內存碎片
補充:分代劃份內存介紹
整個JVM內存總共劃分爲三代:年輕代(Young Generation)、年老代(Old Generation)、(JDK 1.7 沒了)持久代(Permanent Generation)
一、年輕代:全部新生成的對象首先都放在年輕代內存中。年輕代的目標就是儘量快速的手機掉那些生命週期短的對象。年輕代內存分爲一塊較大的Eden空間和兩塊較小的Survior空間,每次使用Eden和其中的一塊Survior.當回收時,將Eden和Survior中還存活的對象一次性拷貝到另一塊Survior空間上,最後清理Eden和剛纔用過的Survior空間。
二、年老代:在年輕代經歷了N次GC後,仍然存活的對象,就會被放在老年代中。所以能夠認爲老年代存放的都是一些生命週期較長的對象。
三、持久代:基本固定不變,用於存放靜態文件,例如Java類和方法。持久代對GC沒有顯著的影響。持久代能夠經過-XX:MaxPermSize=<N>進行設置。
對象沒有引用
做用域發生未捕獲異常
程序在做用域正常執行完畢
程序執行了System.exit()
程序發生意外終止(被殺線程等)
在Java程序中不能顯式的分配和註銷緩存,由於這些事情JVM都幫咱們作了,那就是GC。
有些時候咱們能夠將相關的對象設置成null 來試圖顯示的清除緩存,可是並非設置爲null 就會必定被標記爲可回收,有可能會發生逃逸。
將對象設置成null 至少沒有什麼壞處,可是使用System.gc() 便不可取了,使用System.gc() 時候並非立刻執行GC操做,而是會等待一段時間,甚至不執行,並且System.gc() 若是被執行,會觸發Full GC ,這很是影響性能。
eden區空間不夠存放新對象的時候,執行Minor GC。升到老年代的對象大於老年代剩餘空間的時候執行Full GC,或者小於的時候被HandlePromotionFailure 參數強制Full GC 。調優主要是減小 Full GC 的觸發次數,能夠經過 NewRatio 控制新生代轉老年代的比例,經過MaxTenuringThreshold 設置對象進入老年代的年齡閥值(後面會介紹到)。
在 Java 中,堆被劃分紅兩個不一樣的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young )又被劃分爲
三個區域:Eden、From Survivor、To Survivor。
這樣劃分的目的是爲了使 JVM 可以更好的管理堆內存中的對象,包括內存的分配以及回收。
堆的內存模型大體爲:
從圖中能夠看出: 堆大小 =新生代 + 老年代。其中,堆的大小能夠經過參數 –Xms、-Xmx 來指定。
新生代(Young generation):絕大多數最新被建立的對象都會被分配到這裏,因爲大部分在建立後很快變得不可達,不少對象被建立在新生代,而後「消失」。對象從這個區域「消失」的過程咱們稱之爲:Minor GC 。
老年代(Old generation):對象沒有變得不可達,而且重新生代週期中存活了下來,會被拷貝到這裏。其區域分配的空間要比新生代多。也正因爲其相對大的空間,發生在老年代的GC次數要比新生代少得多。對象從老年代中消失的過程,稱之爲:Major GC 或者 Full GC。
(JDK7後 沒了)持久代(Permanent generation)也稱之爲 方法區(Method area):用於保存類常量以及字符串常量。注意,這個區域不是用於存儲那些從老年代存活下來的對象,這個區域也可能發生GC。發生在這個區域的GC事件也被算爲 Major GC 。只不過在這個區域發生GC的條件很是嚴苛,必須符合如下三種條件纔會被回收:
一、全部實例被回收
二、加載該類的ClassLoader 被回收
三、Class 對象沒法經過任何途徑訪問(包括反射)
可能咱們會有疑問:
若是老年代的對象須要引用新生代的對象,會發生什麼呢?
爲了解決這個問題,老年代中存在一個 card table ,它是一個512byte大小的塊。全部老年代的對象指向新生代對象的引用都會被記錄在這個表中。當針對新生代執行GC的時候,只須要查詢 card table 來決定是否能夠被回收,而不用查詢整個老年代。這個 card table 由一個write barrier 來管理。write barrier給GC帶來了很大的性能提高,雖然由此可能帶來一些開銷,但徹底是值得的。
新生代空間的構成與邏輯
爲了更好的理解GC,咱們來學習新生代的構成,它用來保存那些第一次被建立的對象,它被分紅三個空間:
· 一個伊甸園空間(Eden)
· 兩個倖存者空間(Fron Survivor、To Survivor)
默認新生代空間的分配:Eden : Fron : To = 8 : 1 : 1
老年代空間的構成與邏輯
老年代空間的構成其實很簡單,它不像新生代空間那樣劃分爲幾個區域,它只有一個區域,裏面存儲的對象並不像新生代空間絕大部分都是朝聞道,夕死矣。這裏的對象幾乎都是從Survivor 空間中熬過來的,它們毫不會輕易的狗帶。所以,Full GC(Major GC)發生的次數不會有Minor GC 那麼頻繁,而且作一次Major GC 的時間比Minor GC 要更長(約10倍)。
Java 中的堆也是 GC收集垃圾的主要區域。GC 分爲兩種:Minor GC、FullGC ( 或稱爲 Major GC )。
Minor GC 是發生在新生代中的垃圾收集動做,所採用的是複製算法。新生代幾乎是全部 Java 對象出生的地方,即 Java 對象申請的內存以及存放都是在這個地方。Java 中的大部分對象一般不需長久存活,具備朝生夕滅的性質。當一個對象被斷定爲 "死亡" 的時候,GC 就有責任來回收掉這部分對象的內存空間。新生代是 GC 收集垃圾的頻繁區域。當對象在 Eden ( 包括一個 Survivor 區域,這裏假設是 from 區域 ) 出生後,在通過一次 Minor GC後,如果對象還存活,而且可以被另一塊 Survivor 區域所容納(上面已經假設爲 from 區域,這裏應爲 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用複製算法將這些仍然還存活的對象複製到另一塊 Survivor 區域 ( 即 to 區域 ) 中,而後清理所使用過的 Eden以及 Survivor 區域 ( 即from 區域 ),而且將這些對象的年齡設置爲1,之後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,能夠經過參數 -XX:MaxTenuringThreshold 來設定),這些對象就會成爲老年代。但這也不是必定的,對於一些較大的對象 (即須要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。
Full GC 是發生在老年代的垃圾收集動做,所採用的是標記-清除算法。現實的生活中,老年代的人一般會比新生代的人"早死"。堆內存中的老年代(Old)不一樣於這個,老年代裏面的對象幾乎個個都是在 Survivor 區域中熬過來的,它們是不會那麼容易就 "死掉" 了的。所以,Full GC發生的次數不會有 Minor GC 那麼頻繁,而且作一次 Full GC 要比進行一次 Minor GC 的時間更長。
另外,標記-清除算法收集垃圾的時候會產生許多的內存碎片 (即不連續的內存空間 ),此後須要爲較大的對象分配內存空間時,若沒法找到足夠的連續的內存空間,就會提早觸發一次 GC 的收集動做。
下面只列舉其中的幾個經常使用和容易掌握的配置選項
-Xms |
初始堆大小。如:-Xms256m |
-Xmx |
最大堆大小。如:-Xmx512m |
-Xmn |
新生代大小。一般爲 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間。實際可用空間爲 = Eden + 1 個 Survivor,即 90% |
-Xss |
JDK1.5+ 每一個線程堆棧大小爲 1M,通常來講若是棧不是很深的話, 1M 是絕對夠用了的。 |
-XX:NewRatio |
新生代與老年代的比例,如 –XX:NewRatio=2,則新生代佔整個堆空間的1/3,老年代佔2/3 |
-XX:SurvivorRatio |
新生代中 Eden 與 Survivor 的比值。默認值爲 8。即 Eden 佔新生代空間的 8/10,另外兩個 Survivor 各佔 1/10 |
-XX:PermSize |
永久代(方法區)的初始大小 |
-XX:MaxPermSize |
永久代(方法區)的最大值 |
-XX:+PrintGCDetails |
打印 GC 信息 |
1 full gc頻繁說明old區很快滿了。
2 若是是一次full gc後,剩餘對象很少。那麼說明你eden區設置過小,致使短生命週期的對象進入了old區。
3 若是一次full gc後,old區回收率不大,那麼說明old區過小。
參考
《深刻理解java虛擬機》
http://hllvm.group.iteye.com/group/topic/38223#post-248757
http://www.iteye.com/topic/1119491
http://www.importnew.com/1993.html