java程序员从笨鸟到菜鸟之(九十五)深入java虚拟机_Java程序員從笨鳥到菜鳥之(九十五)深入java虛擬機(四)——java虛擬機的垃圾回收機制 | 學步園...

Java語言從出現到現在,一直佔據java應用程序所運行的平台有關。我們大家都知道java應用程序運行在java虛擬機上。這樣就大大減少了java應用程序和底層操作系統打交道的頻率。這也就為java程序的跨平台提供了良好的基礎。在java虛擬機中為我們提供了一個很重要的機制就是java虛擬機的自動的內存管理機制。也就是我們平時所說的垃圾回收機制,這使得開發人員不用自己來管理應用中的內存。C/C++開發人員需要通過malloc/free 和new/delete等函數來顯式的分配和釋放內存。這對開發人員提出了比較高的要求,容易造成內存訪問錯誤和內存泄露等問題。今天我們就一起來看一下java虛擬機給我們提供的這個強大的功能——自動垃圾回收機制。

我們在c/c++的程序中,他們沒有java中的自動垃圾回收機制,這就需要開發人員手動的去分配和釋放內存,這樣就要求我們的開發人員要有一定的細心和對內存管理的經驗。如果內存管理不好,很容易產生最常見的兩個問題。一是“懸掛引用”,二是內存溢出。所為的懸掛引用就是一個對象引用所指向的內存區塊已經被錯誤的回收並重新分配給新的對象了,程序如果繼續使用這個引用的話會造成不可預期的結果。第二個內存溢出就很好理解了,開發人員在做開發的過程中,只顯示的申請內存而忘記用完釋放掉內存,這樣長時間會導致內存溢出的情況。而像java這種具有自動管理內存機制的語言來說,我們開發人員只需考慮引用的運用就可以,把內存管理這塊交給我們的語言運行環境來管理。。開發人員並不需要關心內存的分配和回收的底層細節。Java平台通過垃圾回收器來進行自動的內存管理。這樣就大大減少了開發人員的工作量

一、Java垃圾回收機制

Java 的垃圾回收器要負責完成3 件任務:

1.分配內存

2.確保被引用的對象的內存不被錯誤回收

3.回收不再被引用的對象的內存空間。

垃圾回收是一個複雜而且耗時的操作。如果JVM 花費過多的時間在垃圾回收上,則勢必會影響應用的運行性能。一般情況下,當垃圾回收器在進行回收操作的時候,整個應用的執行是被暫時中止(stop-the-world)的。這是因為垃圾回收器需要更新應用中所有對象引用的實際內存地址。不同的硬件平台所能支持的垃圾回收方式也不同。比如在多CPU 的平台上,就可以通過並行的方式來回收垃圾。而單CPU 平台則只能串行進行。不同的應用所期望的垃圾回收方式也會有所不同。服務器端應用可能希望在應用的整個運行時間中,花在垃圾回收上的時間總數越小越好。而對於與用戶交互的應用來說,則可能希望所垃圾回收所帶來的應用停頓的時間間隔越小越好。對於這種情況,JVM 中提供了多種垃圾回收方法以及對應的性能調優參數,應用可以根據需要來進行定製。

二、判斷對象是否該被回收

1.引用計數算法

給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1;任何時刻計數器值都為0時對象就表示它不可能被使用了。這個算法實現簡單,但很難解決對象之間循環引用的問題,因此Java並沒有用這種算法!這是很多人都誤解了的地方。

2.根搜索算法

通過一系列名為“GC ROOT”的對象作為起始點,從這些結點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC ROOT沒有任何引用鏈相連時,則證明這個對象是不可用的。如果對象在進行根搜索後發現沒有與GC ROOT相連接的引用鏈,則會被第一次第標記,並看此對象是否需要執行finalize()方法(忘記finalize()這個方法吧,它可以被try-finally或其他方式代替的),當第二次被標記時,對象就會被回收。

三、Java虛擬機基本垃圾回收算法:

1.標記-清除(Mark-Sweep)

此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。它停止所有工作,收集器從根開始訪問每一個活躍的節點,標記它所訪問的每一個節點。走過所有引用後,收集就完成了,然後就對堆進行清除(即對堆中的每一個對象進行檢查),所有沒有標記的對象都作為垃圾回收並返回空閑列表。下圖 展示了垃圾收集之前的堆,陰影塊是垃圾,因為用戶程序不能到達它們:

可到達和不可到達的對象

1349489545_2949.png

標記-清除實現起來很簡單,可以容易地回收循環的結構,並且不像引用計數那樣增加編譯器或者賦值函數的負擔。但是它也有不足――收集暫停可能會很長,在清除階段整個堆都是可訪問的,這對於可能有頁面交換的堆的虛擬內存系統有非常負面的性能影響。

標記-清除的最大問題是,每一個活躍的(即已分配的)對象,不管是不是可到達的,在清除階段都是可以訪問的。因為很多對象都可能成為垃圾,這意思着收集器花費大量精力去檢查並處理垃圾。標記-清除收集器還容易使堆產生碎片,這會產生區域性問題並可以造成分配失敗,即使看來有足夠的自由內存可用。此算法需要暫停整個應用,同時,會產生內存碎片。

1349489574_6954.jpg

2.複製(Copying)

此算法把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。次算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不過出現“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。

1349489615_9522.jpg

3.標記-整理(Mark-Compact)

此算法結合了“標記-清除”和“複製”兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“複製”算法的空間問題。

1349489636_4156.jpg

4.增量收集(Incremental Collecting)

實施垃圾回收算法,即:在應用進行的同時進行垃圾回收。不知道什麼原因JDK5.0中的收集器沒有使用這種算法的。

5.分代(Generational Collecting)

將堆分成新生代(Eden, From Survivor, To Survivor)和老年代,在新生代中使用複製算法,即Minor-GC,當一些對象經過多次的Minor-GC後還留在新生代,則會被搬移到老年代中。而老年代中使用標記-清理或標記-整理算法,即Major GC/Full GC。

-XX:PretenurseSizeThreshold=1024,則大於次參數的對象會直接分配到老年代(儘可能不要寫一些“短命大對象”!)

-XX:MaxTenuringThreshold=15,在survivor空間存活15次之後,則會搬移到老年代

如果是Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代。

進行Minor GC時,虛擬機會檢測之前每次晉陞到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則直接進行一次Full GC。

在對垃圾收集算法進行評價時,我們可能要考慮以下所有標準:

· 暫停時間。收集器是否停止所有工作來進行垃圾收集?要停止多長時間?暫停是否有時間限制?

· 暫停的可預測性。垃圾收集暫停是否規劃為在用戶程序方便而不是垃圾收集器方便的時間發生?

· CPU 佔用。總的可用 CPU時間用在垃圾收集上的百分比是多少?

· 內存大小。許多垃圾收集算法需要將堆分割成獨立的內存空間,其中一些空間在某些時刻對用戶程序是不可訪問的。這意味着堆的實際大小可能比用戶程序的最大堆駐留空間要大幾倍。

· 虛擬內存交互。在具有有限物理內存的系統上,一個完整的垃圾收集在垃圾收集過程中可能會錯誤地將非常駐頁面放到內存中來進行檢查。因為頁面錯誤的成本很高,所以垃圾收集器正確管理引用的區域性 (locality)是很必要的。

· 緩存交互。即使在整個堆可以放到主內存中的系統上 ――實際上幾乎所有Java應用程序都可以做到這一點,垃圾收集也常常會有將用戶程序使用的數據衝出緩存的效果,從而影響用戶程序的性能。

· 對程序區域性的影響。雖然一些人認為垃圾收集器的工作只是收回不可到達的內存,但是其他人認為垃圾收集器還應該盡量改進用戶程序的引用區域性。整理收集器和複製收集器在收集過程中重新安排對象,這有可能改進區域性。

· 編譯器和運行時影響。一些垃圾收集算法要求編譯器或者運行時環境的重要配合,如當進行指針分配時更新引用計數。這增加了編譯器的工作,因為它必須生成這些簿記指令,同時增加了運行時環境的開銷,因為它必須執行這些額外的指令。這些要求對性能有什麼影響呢?它是否會干擾編譯時優化呢?

不管選擇什麼算法,硬件和軟件的發展使垃圾收集更具有實用性。20世紀70和80年代的經驗研究表明,對於大型Lisp程序,垃圾收集消耗25% 到40% 的運行時。垃圾收集還不能做到完全不可見,這肯定還有很長的路要走。

四、三種垃圾回收器

目前的收集器主要有三種:串行收集器、並行收集器、並發收集器。

1.串行收集器

使用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,也無法使用多處理器的優勢,所以此收集器適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。可以使用-XX:+UseSerialGC打開。

2.並行收集器

1)對年輕代進行並行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器機器上使用。使用-XX:+UseParallelGC.打開。並行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中進行了增強--可以堆年老代進行並行收集。如果年老代不使用並發收集的話,是使用單線程進行垃圾回收,因此會制約擴展能力。使用-XX:+UseParallelOldGC打開。

2)使用-XX:ParallelGCThreads=設置並行垃圾回收的線程數。此值可以設置與機器處理器數量相等。

3)此收集器可以進行如下配置:

最大垃圾回收暫停:指定垃圾回收時的最長暫停時間,通過-XX:MaxGCPauseMillis=指定。為毫秒.如果指定了此值的話,堆大小和垃圾回收相關參數會進行調整以達到指定值。設定此值可能會減少應用的吞吐量。

吞吐量:吞吐量為垃圾回收時間與非垃圾回收時間的比值,通過-XX:GCTimeRatio=來設定,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時,表示5%的時間用於垃圾回收。默認情況為99,即1%的時間用於垃圾回收。

3.並發收集器

可以保證大部分工作都並發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的中、大規模應用。使用-XX:+UseConcMarkSweepGC打開。

1)並發收集器主要減少年老代的暫停時間,他在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集初期並發收集器會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程中多個線程同時進行垃圾回收工作。

2)並發收集器使用處理器換來短暫的停頓時間。在一個N個處理器的系統上,並發收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。

3)在只有一個處理器的主機上使用並發收集器,設置為incremental mode模式也可獲得較短的停頓時間。

4)浮動垃圾:由於在應用運行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。所以,並發收集器一般需要20%的預留空間用於這些浮動垃圾。

5)Concurrent Mode Failure:並發收集器在應用運行時進行收集,所以需要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發生“併發模式失敗”,此時整個應用將會暫停,進行垃圾回收。

6)啟動並發收集器:因為並發收集在應用運行時進行收集,所以必須保證收集完成之前有足夠的內存空間供程序使用,否則會出現“Concurrent Mode Failure”。通過設置-XX:CMSInitiatingOccupancyFraction=指定還有多少剩餘堆時開始執行並發收集

五、關於垃圾收集的幾點補充

經過上述的說明,可以發現垃圾回收有以下的幾個特點:

(1)垃圾收集發生的不可預知性:由於實現了不同的垃圾收集算法和採用了不同的收集機制,所以它有可能是定時發生,有可能是當出現系統空閑CPU資源時發生,也有可能是和原始的垃圾收集一樣,等到內存消耗出現極限時發生,這與垃圾收集器的選擇和具體的設置都有關係。

(2)垃圾收集的精確性:主要包括2 個方面:(a)垃圾收集器能夠精確標記活着的對象;(b)垃圾收集器能夠精確地定位對象之間的引用關係。前者是完全地回收所有廢棄對象的前提,否則就可能造成內存泄漏。而後者則是實現歸併和複製等算法的必要條件。所有不可達對象都能夠可靠地得到回收,所有對象都能夠重新分配,允許對象的複製和對象內存的縮並,這樣就有效地防止內存的支離破碎。

(3)現在有許多種不同的垃圾收集器,每種有其算法且其表現各異,既有當垃圾收集開始時就停止應用程序的運行,又有當垃圾收集開始時也允許應用程序的線程運行,還有在同一時間垃圾收集多線程運行。

(4)垃圾收集的實現和具體的JVM 以及JVM的內存模型有非常緊密的關係。不同的JVM 可能採用不同的垃圾收集,而JVM 的內存模型決定着該JVM可以採用哪些類型垃圾收集。現在,HotSpot 系列JVM中的內存系統都採用先進的面向對象的框架設計,這使得該系列JVM都可以採用最先進的垃圾收集。

(5)隨着技術的發展,現代垃圾收集技術提供許多可選的垃圾收集器,而且在配置每種收集器的時候又可以設置不同的參數,這就使得根據不同的應用環境獲得最優的應用性能成為可能。

參考文獻

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值