Java之JVM垃圾回收 內存結構以及垃圾回收算法


前言:由於小組技術分享的需要,懂的不是很多所以我就找了這個我自己感興趣的知識點給大家做個簡單的介紹。由於是新人,算不了很懂,只是總結性的講了些概念性的東西。給大家分享的同時,算是給自己做個筆記吧。

作為Java語言的核心之一,JVM垃圾回收幫我們解決了讓我們很頭疼的垃圾回收問題。我們不需要像VC++一樣,作為內存管理的統治者需要我們對我們分配的每一塊內存進行回收,否則就會造成內存泄露問題。

是不是只要有JVM存在我們就不會出現內存泄露問題,出現內存泄露問題我們又該怎么辦,如果我們想提高我們程序的穩定性和其他性能我們能從什么地方下手!!!相信這些問題是我們程序過程中不可逾越的。了解JVM的內存分配及其相應的垃圾回收機制,不僅僅是可以了解底層的JVM運行機制,而且對於程序性能的優化和提升還是很有必要的。

一、JVM內存分配區域結構

 圖一

從圖一可以看出JVM中的內存分配包括PC Register(PC寄存器)  JVM棧 堆(Heap) 方法區域(MethodArea)運行時常量池(RuntimeConstant Pool) 本地方法堆棧(NativeMethod Stacks),這幾部分區域但是從程序員的角度來看我們只關注JVM Heap和JVM Stack,因為這兩部分是直接關系程序運行期間的內存狀態,所以我會主要介紹這兩部分內存,其他的我只是給出了簡單的一些概念性解釋:

PC Register(Program Counter  寄存器):主要作用是記錄當前線程所執行的字節碼的行號。

方法區域(MethodArea):方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,法區域也是全局共享的,它在虛擬機啟動時在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。

運行時常量池(RuntimeConstant Pool):存放的為類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區域中分配。

本地方法堆棧(NativeMethod Stacks):JVM采用本地方法堆棧來支持native方法的執行,此區域用於存儲每個native方法調用的狀態。

JVM棧:主要存放一些基本類型的變量和對象的引用變量。

JVM堆用來存放由 new 創建的對象和數組Java 虛擬機的自動垃圾回收器來管理(注意數組也是對象,所以說數組也是存放在JVM堆中)。

由於棧中存放的是主要存放一些基本類型的變量和對象的引用變量,所以當過了變量的作用區域或者是當程序運行結束后它所占用的內存會自動的釋放掉,所以不用來關心,下面我們主要來說的是堆內存的分配以及回收的算法。

二、JVM堆內存介紹

 工欲善其事,必先利其器。所以了解堆內存的內部結構是很必要的。

在Jvm中堆空間划分為三個代:年輕代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation)。年輕帶主要是動態的存儲,年輕帶主要儲存新產生的對象,年老代儲存年齡大些的對象,永久帶主要是存儲的是java的類信息,包括解析得到的方法、屬性、字段等。永久帶基本不參與垃圾回收。所以說我們說的垃圾回收主要是針對年輕代和年老代。

 

 

 

 

圖二

 

年輕代又分成3個部分,一個eden區和兩個相同的survior區。剛開始創建的對象都是放置在eden區的。分成這樣3個部分,主要是為了生命周期短的對象盡量留在年輕帶。當eden區申請不到空間的時候,進行minorGC,把存活的對象拷貝到survior。年老代主要存放生命周期比較長的對象,比如緩存對象。(經過IBM的一個研究機構研究數據表明,基本上80%-98%的對象都會在年輕代的Eden區死掉從而本回收掉,所以說真正進入到老年代的對象很少,這也是為什么MinorGC比MajorGC更加頻繁的原因)

具體JVM內存垃圾回收過程描述如下 :

1、對象在Eden區完成內存分配
2、當Eden區滿了,再創建對象,會因為申請不到空間,觸發minorGC,進行young(eden+1survivor)區的垃圾回收
3、minorGC時,Eden不能被回收的對象被放入到空的survivor(Eden肯定會被清空),另一個survivor里不能被GC回收的對象也會被放入這個survivor,始終保證一個survivor是空的
4、當做第3步的時候,如果發現survivor滿了,則這些對象被copy到old區,或者survivor並沒有滿,但是有些對象已經足夠Old,也被放入Old區 XX:MaxTenuringThreshold
5、當Old區被放滿的之后,進行fullGC

補充: MinorGC:年輕代所進行的垃圾回收,非常頻繁,一般回收速度也比較快。

         MajorGC:老年代進行的垃圾回收,發生一次MajorGC至少伴隨一次MinorGC,一般比MinorGC速度慢十倍以上。

         FullGC:整個堆內存進行的垃圾回收,很多時候是MajorGC

以后就是堆內存結構已經大致的垃圾回收過程。

三、對象分配原則

 

1.對象優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。

2.大對象直接進入老年代(大對象是指需要大量連續內存空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代采用復制算法收集內存)。

3.長期存活的對象進入老年代。虛擬機為每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那么對象會進入Survivor區,之后每經過一次Minor GC那么對象的年齡加1,知道達到閥值對象進入老年區。

4.動態判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代。

5.空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩余值大小則進行一次Full GC,如果小於檢查HandlePromotionFailure設置,如果true則只進行Monitor GC,如果false則進行Full GC。

 

四、垃圾收集器

作為JVM中的核心之一垃圾收集器,主要完成的功能包括:(1)發現無用信息對象;(2)回收被無用對象占用的內存空間,使該空間可被程序再次使用。所以說我們在實現垃圾收集器的同時就要實現兩個算法一個是發現無用的對象第二就是回收該對象的內存。

收集器主要分為引用計數器和跟蹤收集器兩種,Sun JDK中采用跟蹤收集器作為GC實現策略。發現無用對象只要的實現算法包括引用計數法和根搜索算法,引用計數法主要是JVM的早期實現方法,因為引用計數無法解決循環引用的問題,所以現在JVM實現的主要是根搜索算法,

引用計數法:堆中的每個對象對應一個引用計數器。當每一次創建一個對象並賦給一個變量時,引用計數器置為1。當對象被賦給任意變量時,引用計數器每次加1當對象出了作用域后(該對象丟棄不再使用),引用計數器減1,一旦引用計數器為0,對象就不可用從而可以被回收。 
根搜索算法通過一系列的名為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

目前的收集器主要有三種
串行收集器:使用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高
並行收集器:對年輕代進行並行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器機器上使用
並發收集器:可以保證大部分工作都並發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的中、大規模應用

五、垃圾收集器的回收算法

Copying算法:

 算法:復制采用的方式為從根集合掃描出存活的對象,並將找到的存活對象復制到一塊新的完全未使用的空間中。
 過程:  此算法把內存空間划為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另外一個區域中。次算法每次只處理正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內存整理,不過出現 碎片 問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。
 
Mark-Sweep算法:
 算法:標記-清除采用的方式為從根集合開始掃描,對存活的對象進行標記,標記完畢后,再掃描整個空間中未標記的對象,並進行回收。
 過程: 第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。它停止所有工作,收集器從根開始訪問每一個活躍的節點,標記它所訪問的每一個節點。走過所有引用后,收集就完成了,然后就對堆進行清除(即對堆中的每一個對象進行檢查),所有沒有標記的對象都作為垃圾回收並返回空閑列表。
Mark-Compact算法:
 算法:標記階段與“Mark-Sweep”算法相同,但在清除階段有所不同。在回收不存活對象所占用的內存空間后,會將其他所有存活對象都往左端空閑的空間進行移動,並更新引用其對象指針。
過程: 此算法結合了 標記 - 清除 復制 兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象 壓縮 到堆的其中一塊,按順序排放。此算法避免了 標記 - 清除 的碎片問題,同時也避免了 復制 算法的空間問題。

Sun JDK GC策略:

新生代算法實現:Copying,Copying,Copying

舊生代算發實現:Mark-Sweep-Compact,Mark –Compact,Mark –Sweep!!

六、JvisuaVM 工具

如果我們想優化自己的程序,那么我們就必須清楚的了解不同代碼程序所消耗的性能多少,作為JDK的一部分,這個工具給我們提供了很大的幫助。這個工具可以在JDK的bin目錄下找到,功能很強大,可以注意利用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM