JVM內存模型以及垃圾收集策略解析文章分類:Java編程首先祝大家春節(jié)愉快,幾個月前研究了一下JVM的內存模型,整理學習心得,共享出來和大家一起學習討論進步。
一 JVM內存模型1.1 Java棧Java棧是與每一個線程關聯(lián)的,JVM在創(chuàng)建每一個線程的時候,會分配一定的??臻g給線程。它主要用來存儲線程執(zhí)行過程中的局部變量,方法的返回值,以及方法調用上下文。棧空間隨著線程的終止而釋放。 StackOverflowError:如果在線程執(zhí)行的過程中,??臻g不夠用,那么JVM就會拋出此異常,這種情況一般是死遞歸造成的。 1.2 堆Java中堆是由所有的線程共享的一塊內存區(qū)域,堆用來保存各種JAVA對象,比如數(shù)組,線程對象等。 1.2.1 GenerationJVM堆一般又可以分為以下三部分: Ø Perm Perm代主要保存class,method,filed對象,這部門的空間一般不會溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應用服務器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署后,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啟動應用服務器可以解決問題。 Ø Tenured Tenured區(qū)主要保存生命周期長的對象,一般是一些老的對象,當一些對象在Young復制轉移一定的次數(shù)以后,對象就會被轉移到Tenured區(qū),一般如果系統(tǒng)中用了application級別的緩存,緩存中的對象往往會被轉移到這一區(qū)間。 Ø Young Young區(qū)被劃分為三部分,Eden區(qū)和兩個大小嚴格相同的Survivor區(qū),其中Survivor區(qū)間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時復制對象用,在Young區(qū)間變滿的時候,minor GC就會將存活的對象移到空閑的Survivor區(qū)間中,根據(jù)JVM的策略,在經(jīng)過幾次垃圾收集后,任然存活于Survivor的對象將被移動到Tenured區(qū)間。 1.2.2 Sizing the GenerationsJVM提供了相應的參數(shù)來對內存大小進行配置。 正如上面描述,JVM中堆被分為了3個大的區(qū)間,同時JVM也提供了一些選項對Young,Tenured的大小進行控制。 Ø Total Heap -Xms :指定了JVM初始啟動以后初始化內存 -Xmx:指定JVM堆得最大內存,在JVM啟動以后,會分配-Xmx參數(shù)指定大小的內存給JVM,但是不一定全部使用,JVM會根據(jù)-Xms參數(shù)來調節(jié)真正用于JVM的內存 -Xmx -Xms之差就是三個Virtual空間的大小 Ø Young Generation -XX:NewRatio=8意味著tenured 和 young的比值8:1,這樣eden+2*survivor=1/9 堆內存 -XX:SurvivorRatio=32意味著eden和一個survivor的比值是32:1,這樣一個Survivor就占Young區(qū)的1/34. -Xmn 參數(shù)設置了年輕代的大小 Ø Perm Generation -XX:PermSize=16M -XX:MaxPermSize=64M Thread Stack -XX:Xss=128K
1.3 堆棧分離的好處
呵呵,其它的先不說了,就來說說面向對象的設計吧,當然除了面向對象的設計帶來的維護性,復用性和擴展性方面的好處外,我們看看面向對象如何巧妙的利用了堆棧分離。如果從JAVA內存模型的角度去理解面向對象的設計,我們就會發(fā)現(xiàn)對象它完美的表示了堆和棧,對象的數(shù)據(jù)放在堆中,而我們編寫的那些方法一般都是運行在棧中,因此面向對象的設計是一種非常完美的設計方式,它完美的統(tǒng)一了數(shù)據(jù)存儲和運行。
二 JAVA垃圾收集器2.1 垃圾收集簡史垃圾收集提供了內存管理的機制,使得應用程序不需要在關注內存如何釋放,內存用完后,垃圾收集會進行收集,這樣就減輕了因為人為的管理內存而造成的錯誤,比如在C++語言里,出現(xiàn)內存泄露時很常見的。 Java語言是目前使用最多的依賴于垃圾收集器的語言,但是垃圾收集器策略從20世紀60年代就已經(jīng)流行起來了,比如Smalltalk,Eiffel等編程語言也集成了垃圾收集器的機制。 2.2 常見的垃圾收集策略所有的垃圾收集算法都面臨同一個問題,那就是找出應用程序不可到達的內存塊,將其釋放,這里面得不可到達主要是指應用程序已經(jīng)沒有內存塊的引用了,而在JAVA中,某個對象對應用程序是可到達的是指:這個對象被根(根主要是指類的靜態(tài)變量,或者活躍在所有線程棧的對象的引用)引用或者對象被另一個可到達的對象引用。 2.2.1 Reference Counting(引用計數(shù))引用計數(shù)是最簡單直接的一種方式,這種方式在每一個對象中增加一個引用的計數(shù),這個計數(shù)代表當前程序有多少個引用引用了此對象,如果此對象的引用計數(shù)變?yōu)?span style="FONT-FAMILY: Times New Roman">0,那么此對象就可以作為垃圾收集器的目標對象來收集。 優(yōu)點: 簡單,直接,不需要暫停整個應用 缺點: 1.需要編譯器的配合,編譯器要生成特殊的指令來進行引用計數(shù)的操作,比如每次將對象賦值給新的引用,或者者對象的引用超出了作用域等。 2.不能處理循環(huán)引用的問題 2.2.2 跟蹤收集器跟蹤收集器首先要暫停整個應用程序,然后開始從根對象掃描整個堆,判斷掃描的對象是否有對象引用,這里面有三個問題需要搞清楚: 1.如果每次掃描整個堆,那么勢必讓GC的時間變長,從而影響了應用本身的執(zhí)行。因此在JVM里面采用了分代收集,在新生代收集的時候minor gc只需要掃描新生代,而不需要掃描老生代。 2.JVM采用了分代收集以后,minor gc只掃描新生代,但是minor gc怎么判斷是否有老生代的對象引用了新生代的對象,JVM采用了卡片標記的策略,卡片標記將老生代分成了一塊一塊的,劃分以后的每一個塊就叫做一個卡片,JVM采用卡表維護了每一個塊的狀態(tài),當JAVA程序運行的時候,如果發(fā)現(xiàn)老生代對象引用或者釋放了新生代對象的引用,那么就JVM就將卡表的狀態(tài)設置為臟狀態(tài),這樣每次minor gc的時候就會只掃描被標記為臟狀態(tài)的卡片,而不需要掃描整個堆。具體如下圖: 3.GC在收集一個對象的時候會判斷是否有引用指向對象,在JAVA中的引用主要有四種:Strong reference,Soft reference,Weak reference,Phantom reference. Ø Strong Reference 強引用是JAVA中默認采用的一種方式,我們平時創(chuàng)建的引用都屬于強引用。如果一個對象沒有強引用,那么對象就會被回收。 public void testStrongReference(){ Object referent = new Object(); Object strongReference = referent; referent = null; System.gc(); assertNotNull(strongReference); }
Ø Soft Reference 軟引用的對象在GC的時候不會被回收,只有當內存不夠用的時候才會真正的回收,因此軟引用適合緩存的場合,這樣使得緩存中的對象可以盡量的再內存中待長久一點。 Public void testSoftReference(){ String str = "test"; SoftReference<String> softreference = new SoftReference<String>(str); str=null; System.gc(); assertNotNull(softreference.get()); }
Ø Weak reference 弱引用有利于對象更快的被回收,假如一個對象沒有強引用只有弱引用,那么在GC后,這個對象肯定會被回收。 Public void testWeakReference(){ String str = "test"; WeakReference<String> weakReference = new WeakReference<String>(str); str=null; System.gc(); assertNull(weakReference.get()); }
Ø Phantom reference 2.2.2.1 Mark-Sweep Collector(標記-清除收集器)標記清除收集器最早由Lisp的發(fā)明人于1960年提出,標記清除收集器停止所有的工作,從根掃描每個活躍的對象,然后標記掃描過的對象,標記完成以后,清除那些沒有被標記的對象。 優(yōu)點: 1 解決循環(huán)引用的問題 2 不需要編譯器的配合,從而就不執(zhí)行額外的指令 缺點: 1.每個活躍的對象都要進行掃描,收集暫停的時間比較長。 2.2.2.2 Copying Collector(復制收集器)復制收集器將內存分為兩塊一樣大小空間,某一個時刻,只有一個空間處于活躍的狀態(tài),當活躍的空間滿的時候,GC就會將活躍的對象復制到未使用的空間中去,原來不活躍的空間就變?yōu)榱嘶钴S的空間。 復制收集器具體過程可以參考下圖: 優(yōu)點: 1 只掃描可以到達的對象,不需要掃描所有的對象,從而減少了應用暫停的時間 缺點: 1.需要額外的空間消耗,某一個時刻,總是有一塊內存處于未使用狀態(tài) 2.復制對象需要一定的開銷 2.2.2.3 Mark-Compact Collector(標記-整理收集器)標記整理收集器汲取了標記清除和復制收集器的優(yōu)點,它分兩個階段執(zhí)行,在第一個階段,首先掃描所有活躍的對象,并標記所有活躍的對象,第二個階段首先清除未標記的對象,然后將活躍的的對象復制到堆得底部。標記整理收集器的過程示意圖請參考下圖:
Mark-compact策略極大的減少了內存碎片,并且不需要像Copy Collector一樣需要兩倍的空間。 2.3 JVM的垃圾收集策略GC的執(zhí)行時要耗費一定的CPU資源和時間的,因此在JDK1.2以后,JVM引入了分代收集的策略,其中對新生代采用"Mark-Compact"策略,而對老生代采用了“Mark-Sweep"的策略。其中新生代的垃圾收集器命名為“minor gc”,老生代的GC命名為"Full Gc 或者Major GC".其中用System.gc()強制執(zhí)行的是Full Gc. 2.3.1 Serial CollectorSerial Collector是指任何時刻都只有一個線程進行垃圾收集,這種策略有一個名字“stop the whole world",它需要停止整個應用的執(zhí)行。這種類型的收集器適合于單CPU的機器。 Serial Copying Collector 此種GC用-XX:UseSerialGC選項配置,它只用于新生代對象的收集。1.5.0以后. -XX:MaxTenuringThreshold來設置對象復制的次數(shù)。當eden空間不夠的時候,GC會將eden的活躍對象和一個名叫From survivor空間中尚不夠資格放入Old代的對象復制到另外一個名字叫To Survivor的空間。而此參數(shù)就是用來說明到底From survivor中的哪些對象不夠資格,假如這個參數(shù)設置為31,那么也就是說只有對象復制31次以后才算是有資格的對象。 這里需要注意幾個個問題: Ø From Survivor和To survivor的角色是不斷的變化的,同一時間只有一塊空間處于使用狀態(tài),這個空間就叫做From Survivor區(qū),當復制一次后角色就發(fā)生了變化。 Ø 如果復制的過程中發(fā)現(xiàn)To survivor空間已經(jīng)滿了,那么就直接復制到old generation. Ø 比較大的對象也會直接復制到Old generation,在開發(fā)中,我們應該盡量避免這種情況的發(fā)生。 Serial Mark-Compact Collector 串行的標記-整理收集器是JDK5 update6之前默認的老生代的垃圾收集器,此收集使得內存碎片最少化,但是它需要暫停的時間比較長 2.3.2 Parallel CollectorParallel Collector主要是為了應對多CPU,大數(shù)據(jù)量的環(huán)境。 Parallel Collector又可以分為以下兩種: Parallel Copying Collector 此種GC用-XX:UseParNewGC參數(shù)配置,它主要用于新生代的收集,此GC可以配合CMS一起使用。1.4.1以后 Parallel Mark-Compact Collector 此種GC用-XX:UseParallelOldGC參數(shù)配置,此GC主要用于老生代對象的收集。1.6.0 Parallel scavenging Collector 此種GC用-XX:UseParallelGC參數(shù)配置,它是對新生代對象的垃圾收集器,但是它不能和CMS配合使用,它適合于比較大新生代的情況,此收集器起始于jdk 1.4.0。它比較適合于對吞吐量高于暫停時間的場合。 Serial gc和Parallel gc可以用如下的圖來表示: 2.3.3 Concurrent CollectorConcurrent Collector通過并行的方式進行垃圾收集,這樣就減少了垃圾收集器收集一次的時間,這種GC在實時性要求高于吞吐量的時候比較有用。 此種GC可以用參數(shù)-XX:UseConcMarkSweepGC配置,此GC主要用于老生代和Perm代的收集。
參考資料 1 http://developers./mobility/midp/articles/garbage/ 2 http://developers./mobility/midp/articles/garbagecollection2/ 3 http://blogs./watt/resource/jvm-options-list.html 4 http://java./developer/technicalArticles/Programming/turbo/ 5 http://www.ibm.com/developerworks/library/j-jtp10283/index.html?S_TACT=105AGX52&S_CMP=cn-a-j 6 http://www.ibm.com/developerworks/library/j-jtp11253/index.html?S_TACT=105AGX52&S_CMP=cn-a-j |
|
來自: citycanyon > 《基本資料》