一直以來想寫博客,而沒有寫。今天下定決心開始培養寫博客的習慣。第一篇博客,不會寫的太深。主要談談java關於gc方面的知識。若是有紕漏,也請你們多多指教。 java
本文主要是針對Hotspot虛擬機討論。其它的虛擬機可能有所不一樣,請讀者自行區別呵。由於咱們一般用的也是Hotspot。嘿嘿,開源,免費嘛! 算法
衆所周知,java對內存的回收是由gc自動回收。這就不得不說java中有哪些內存須要回收,那麼就得先看看java有哪些內存區域。 多線程
java運行時的內存區域: 併發
1.java堆,這是java程序中應用最多的一塊內存區域。咱們new一個對象的內存都在這裏分配。 jvm
2.方法區,這是java程序的代碼區域。類,方法,變量的信息都存在這個區域,固然常量池也在這個區域。 jsp
3.虛擬機棧,java程序是支持多線程的程序,也是基於棧解釋的程序。因此程序調用方法的壓棧,出棧就是指的這個虛擬機棧。虛擬機棧主要是分配對象的引用和基本類型。 性能
4.本地方法棧,java程序是支持native方法的程序。那麼native方法棧的空間將在這部份內存分配。 spa
5.程序計數器,這個用到的內存極少,不少時候不須要考慮。固然這個是和一個線程綁定的。存放程序命令執行到的位置。 操作系統
6.直接內存,須要強調這個不是在jvm進程的內存空間,而是用的操做系統的內存。之因此把它寫到這裏,是要提醒你們千萬別忘了這塊內存。這也是容易發生OOM的地方。特別是NIO的時候。下面會具體談。 .net
能夠看出這些內存區域,1,2,6內存區域是全局共享的,也就是全部的線程都共用這些內存空間。3,4,5是線程綁定的,也就是說這些內存空間的生命週期是和線程息息相關的。那麼3,4,5的內存釋放就很是簡單了。好比虛擬機棧,隨着壓棧內存分配,出棧內存釋放。所謂壓棧,出棧就是方法調用和方法退出。本地方法棧也同樣。程序計數器隨線程的回收而回收。下面重點談談1,2,6的內存回收。
關於java堆的內存回收:
毫無疑問這塊內存是java中最活躍的內存區域。全部的java new一個對象都要在這裏完成內存分配。當這塊內存在發生gc後任然不能分配對象時,將發生OOM。java.lang.OutOfMemoryError: ......java heap space。這個一般是最容易產生的內存溢出。若是發生了這類OOM就要考慮是否堆內存設置得過小,或者程序是否是一直在分配內存而且持有強引用,使這些對象一直是強可及對象?好了,直接進入正題說這塊內存是如何回收的。要知道如何回收先要知道關於任何gc都須要解決的問題。
1.內存中有哪些對象,哪些對象又是能夠回收的,哪些對象是不能回收的
2.如何回收,怎麼把須要回收的對象從內存中移除。
3,何時須要回收呢。
4.如何解決回收時程序的延遲,以及發生gc時的內存分配請求。特別是多線程程序,在發生gc時候必然還有不少線程在請求堆內存分配。怎麼處理這些請求。
下面對這些問題一一解答。請記住問題的編號,下面會提到問題的編號。
關於第1個問題——java中找到對象是用的根搜索算法(不是引用計數)。那麼java中的根如何斷定呢。java中的根主要存在在這些地方:實例變量,靜態變量,方法棧上的引用,本地方法棧的引用。也就是說java只能在這裏找到如今程序還在使用的對象,不能回收的對象。那麼怎麼找到不須要的對象呢。嘿嘿,固然就是對內存中的全部變量進行標記,把須要使用的對象通通標記出來,打過標記的對象就是要用的,不要回收掉了。這裏就產生了一系列問題呵,這些問題也是用來解答問題4的。java的內存能夠分配N個G,那麼這麼多的對象,每次都標記,得要多久啊。何況java中有些對象是程序啓動到結束一直都在堆裏面的。每次都來標記它,而後又不清除,這多浪費啊!因此java的gc強大之處就展示出來了。
關於問題2如何回收——java採用分代回收。java中的堆內存分紅兩個代:新生代,老年代。
所謂新生代: 能夠從字面上理解,就是那些還比較年輕的對象。就是"才"分配出來的對象。新生代的對象會根據必定的晉升策略進入老年代。晉升策略有(可能不全):
1,new一個對象這個對象足夠大(默認是超過新生代內存的一半,這個能夠配置),那麼直接進入老年代。
2,當一個對象在新生代存活了必定次數(默認是15次,這個次數仍是有點多呵,能夠根據狀況配置),就進入老年代。
3,在新生代發生gc時,「內存不足」(這裏關係到新生代的內存回收策略,等會會講到,也會說明爲何會不足),要老年代提供擔保時。新生代多出的對象直接進入老年代。
新生代的回收策略: 新生代也分爲兩個區域:一部分叫作Eden區,樂透區,新生代會首先在這個區域分配對象。另外一部分,由兩塊對等的區域組成,叫作Survivor。爲何會由兩部分組成呢。這就不得不重新生代的特性提及了。在新生代請求分配內存最多的地方毫無疑問是方法棧中。一般隨着方法的退棧,大多數對象都不是根可及對象了。因此這裏的大多數對象都是能夠回收的了。那麼對這個些對象進行標記而且回收,那麼回收率是很是高的。那採用哪一種標記回收策略呢?這裏主要有3種標記回收策略:
1,標記複製——標記可用對象而後複製到另外一塊內存區域,沒有內存碎片。
2,標記清除——標記可用對象,而後對於不可用對象直接清除,這裏必然產生內存碎片。
3,標記整理——標記可用對象,而後把可用對象往一段移動,記錄可用對象區域,把可用對象另外一端的對象清除掉。沒有內存碎片。
顯然對於新生代這種大多數對象在很快就能夠被回收的狀況,採用1,3種策略最合適,由於須要複製或移動的對象不多,同時不會產生內存碎片。GC在新生代一般採用的是標記複製算法。而且基於大多數對象能夠被回收的假設,複製的兩個區域大小不是對等的,即Eden和Survivor的大小不是對等的。默認配置是8:1。因爲Survivor有兩塊對等的。因此新生代Eden區默認會佔到五分之四。當發生GC時,會把Eden區和其中一塊Survivor往另一塊Survivor複製。這個時候,另外一塊Survivor可能不能裝下Eden和那一塊Survivor的對象,若是這種狀況發生就須要老年代擔保,並直接進入老年代。新生代就這樣來回複製,實現GC回收。在回收的過程當中是要暫停內存的分配。因此這裏是一個程序停頓點。一般年輕代的回收會很快,因此這裏的停頓時間不會太長。可是年輕代的回收頻率每每是較高的。固然年輕代的垃圾回收也能夠配置單線程,多線程,可控制延時等回收器。
老年代的回收策略:這裏主要講cms的回收策略。至於G1或JDK1.5之前的一些老年代回收策略如今不多使用,這裏暫時不解釋。cms(concurrent-mark-sweep)從名字上來講,他是並行的老年代回收器。可是這不意味着全部老年代gc的過程都是能夠和內存分配並行的。老年代回收分爲下面幾個過程:
1,初始化標記
2,併發標記
3,從新標記
4,併發清除
對於1,3過程對於內存的分配請求也是停頓的,對於2,4過程能夠和內存的分配請求同時進行。老年代是用的標記清除算法,因此必然產生內存碎片。這能夠開啓內存碎片整理,在多少次GC後進行一次內存碎片整理(這是能夠配置的)。這個整理過程是full GC 是讓應用程序停頓的。
關於第3個問題——何時回收。對於新生代,一般是對象沒法分配的時候會發生gc。對於老年代是到達必定的內存使用率發生一次gc。默認配置下的使用率是68%.這也是能夠配置項。對於full gc可能在顯示調用System.gc()後發生。固然有參數能夠關閉顯示調用gc。DisableExplicitGC參數設置成false,就不能顯示的調用gc了。固然還有其它發生gc的狀況,好比是否配置了容許擔保失敗呀。
關於第4個問題——分代回收是提高java性能的關鍵,cms的並行回收在必定程度上知足了gc和應用程序同時運行,減小了停頓時間。這也是java語言和虛擬機走向成熟,倍受關注,普遍使用的緣由。固然很期待G1回收器的使用。這個並行,能夠控制停頓時間,標記整理的老年代收集器,必然是java虛擬器的趨勢。也是java語言走向更輝煌的一個指望。
關於方法區的內存回收:
通常來講方法區的內存分配和回收都不會像java堆那樣頻發。但這也不是真正意義上的永久代,這裏仍是會發生內存回收的。通常來講對於一個類的回收須要知足一下條件才能被回收(可能不全)
1,該類的全部實例都已經被回收
2,類的加載器已經被回收
3,類對應的class沒有任何地方在使用,包括反射也不會用到。
這些條件其實比較苛刻了。在通常的應用程序中可能不會遇到。可是若是頻繁使用cglib生成類或動態加載類或熱部署jsp等應用程序裏面仍是會發生。
對於常量的回收,要知足常量沒有任何強可及的引用。
關於直接內存的回收:
什麼地方會使用到直接內存。通常來講是在NIO中。好比netty是默認是用的直接內存分配,mina在2.0後已經不是默認使用的直接內存了。以前已經強調了,直接內存不是使用的jvm進程的內存,而是使用操做系統的內存。這部份內存也能夠在jvm啓動參數裏面配置。MaxDirectMemorySize。對於這塊內存的回收每每要在full gc的時候才能回收。之前公司是有用netty的地方(Hbase的通訊)發生了OOM,就是由於老年代的回收頻率過低了,在直接內存溢出了也沒有發生老年代回收。固然這裏能夠打開DisableExplicitGC來解決。不過這也是一個危險的操做,意味着你容許了應用程序顯示的調用System.gc(),因此在應用程序裏面最好不要這麼調用。打開DisableExplicitGc,應該把相應的並行回收參數也打開,否則這裏就是一個真正意義上的full gc,意味着程序是停頓的。至於具體參數能夠查看jvm參數配置,這裏不貼出來了。對於直接內存的回收還能夠顯示調用,DirectBuffer的cleaner的clean方法,具體能夠參照另外一個同窗的博客http://blog.csdn.net/xieyuooo/article/details/7547435。裏面介紹的很詳細。
關於java的gc就簡單介紹到這裏了,固然java的gc還有不少複雜的應用。java的核心和優點之一就是強大的gc。平時能夠多觀察下java gc的狀況,這對掌握應用程序的性能是很是有幫助的。整個jvm調優都是要基於gc的狀況調整的。
若是本文有任何紕漏還請你們多多指正。