JVM性能優化, Part 3 垃圾回收

ImportNew注:本文是JVM性能優化 系列-第3篇-《JVM性能優化, Part 3 —— 垃圾回收html

第一篇 《JVM性能優化, Part 1 ―― JVM簡介 》java

第二篇《JVM性能優化, Part 2 ―― 編譯器git

Java平臺的垃圾回收機制大大提升的開發人員的生產力,但實現糟糕的垃圾回收器卻會大大消耗應用程序的資源。本文做爲JVM性能優化系列的第3篇,Eva Andeasson將爲Java初學者介紹Java平臺的內存模型和GC機制。她將解釋爲何碎片化(不是GC)是Java應用程序出現性能問題的主要緣由,以及爲何當前主要經過分代垃圾回收和壓縮,而不是其餘最具創意的方法,來解決Java應用程序中碎片化的問題。程序員

垃圾回收(GC)是旨在釋放不可達Java對象所佔用的內存的過程,是Java virtual machine(JVM)中動態內存管理系統的核心組成部分。在一個典型的垃圾回收週期中,全部仍被引用的對象,便可達對象,會被保留。沒有被引用的Java對象所佔用的內存會被釋放並回收,以便分配給新建立的對象。github

爲了更好的理解垃圾回收與各類不一樣的GC算法,你首先須要瞭解一些關於Java平臺內存模型的內容。算法

垃圾回收與Java平臺內存模型編程

當你在啓動Java應用程序時指定了啓動參數_-Xmx_(例如,java -Xmx2g MyApp),則相應大小的內存會被分配給Java進程。這塊內存即所謂的*Java堆*(或簡稱爲*堆*)。這塊專用的內存地址空間用於存儲Java應用程序(有時是JVM)所建立的對象。隨着Java應用程序的運行,會不斷的建立新對象併爲之分配內存,Java堆(即地址空間)會逐漸被填滿。安全

最後,Java堆會被填滿,這就是說想要申請內存的線程沒法得到一塊足夠大的連續空閒空間來存放新建立的對象。此時,JVM判斷須要啓動垃圾回收器來回收內存了。當Java程序調用System.gc()方法時,也有可能會觸發垃圾回收器以執行垃圾回收的工做。使用System.gc()方法並不能保證垃圾回收工做確定會被執行。在執行垃圾回收前,垃圾回收機制首先會檢查當前是不是一個「恰當的時機」,而「恰當的時機」指全部的應用程序活動線程都處於安全點(safe point),以便啓動垃圾回收。簡單舉例,爲對象分配內存時,或正在優化CPU指令(參見本系列的前一篇文章)時,就不是「恰當的時機」,由於你可能會丟失上下文信息,從而獲得混亂的結果。性能優化

垃圾回收不該該回收當前有活動引用指向的對象所佔用的內存;由於這樣作將違反JVM規範。在JVM規範中,並無強制要求垃圾回收器當即回收已死對象(dead object)。已死對象最終會在後續的垃圾回收週期中被釋放掉。目前,已經有多種垃圾回收的實現,它們都包含兩個溝通的假設。對垃圾回收來講,真正的挑戰在於標識出全部活動對象(即仍有引用指向的對象),回收全部不可達對象所佔用的內存,並儘量不對正在運行的應用程序產生影響。所以,垃圾回收器運行的兩個目標:
服務器

  1. 快速釋放不可達對象所佔用的內存,防止應用程序出現OOM錯誤。

  2. 回收內存時,對應用程序的性能(指延遲和吞吐量)的影響要緊性能小。

兩類垃圾回收

在本系列的第一篇文章中,我提到了2種主要的垃圾回收方式,引用計數(reference counting)和引用追蹤(tracing collector。譯者注,在第一篇中,給出的名字是「reference tracing」,這裏仍沿用以前的名字)。這裏,我將深刻這兩種垃圾回收方式,並介紹用於生產環境的實現了引用追蹤的垃圾回收方式的相關算法。

     相關閱讀:JVM性能優化系列

  •            JVM性能優化,第一部分: 概述

  •            JVM性能優化,第二部分: 編譯器

引用計數垃圾回收器

引用計數垃圾回收器會對指向每一個Java對象的引用數進行跟蹤。一旦發現指向某個對象的引用數爲0,則當即回收該對象所佔用的內存。引用計數垃圾回收的主要優勢就在於能夠當即訪問被回收的內存。垃圾回收器維護未被引用的內存並不須要消耗很大的資源,可是保持並不斷更新引用計數卻代價不菲。

使用引用計數方式執行垃圾回收的主要困難在於保持引用計數的準確性,而另外一個衆所周知的問題在於解決循環引用結構所帶來的麻煩。若是兩個對象互相引用,而且沒有其餘存活東西引用它們,那麼這兩個對象所佔用的內存將永遠不會被釋放,兩個對象都會因引用計數不爲0而永遠存活下去。要解決循環引用帶來的問題須要,而這會使算法複雜度增長,從而影響應用程序的運行性能。

引用跟蹤垃圾回收

引用跟蹤垃圾回收器基於這樣一種假設,全部存活對象均可以經過迭代地跟蹤從已知存活對象集中對象發出的引用及引用的引用來找到。能夠經過對寄存器、全局域、以及觸發垃圾回收時棧幀的分析來肯定初始存活對象的集合(稱爲「根對象」,或簡稱爲「根」)。在肯定了初始存活對象集後,引用跟蹤垃圾回收器會跟蹤從這些對象中發出的引用,並將找到的對象標記爲「活的(live)」。標記全部找到的對象意味着已知存活對象的集合會隨時間而增加。這個過程會一直持續到全部被引用的對象(所以是「存活的」對象)都被標記。當引用跟蹤垃圾回收器找到全部存活的對象後,就會開始回收未被標記的對象。

不一樣於引用計數垃圾回收器,引用跟蹤垃圾回收器能夠解決循環引用的問題。因爲標記階段的存在,大多數引用跟蹤垃圾回收器沒法當即釋放「已死」對象所佔用的內存。

引用跟蹤垃圾回收器普遍用於動態語言的內存管理;到目前爲止,在Java編程語言的視線中也是應用最廣的,而且在多年的商業生產環境中,已經證實其實用性。在本文餘下的內容中,我將從一些相關的實現算法開始,介紹引用跟蹤垃圾回收器,

引用跟蹤垃圾回收器算法

拷貝和*標記-清理*垃圾回收算法並不是新近發明,但仍然是當今實現引用跟蹤垃圾回收器最經常使用的兩種算法。

拷貝垃圾回收器

傳統的拷貝垃圾回收器會使用一個「from」區和一個「to」區,它們是堆中兩個不一樣的地址空間。在執行垃圾回收時,from區中存活對象會被拷貝到to區。當from區中全部的存活對象都被拷貝到to後,垃圾回收器會回收整個from區。當再次分配內存時,會首先從to區中的空閒地址開始分配。

在該算法的早期實現中,from區和to區會在垃圾回收週期後進行交換,即當to區被填滿後,將再次啓動垃圾回收,這是to區會「變成」from區。如圖Figure 1所示。

Figure 1. A traditional copying garbage collection sequence

在該算法的近期實現中,能夠將堆中任意地址空間指定爲from區和to區,這樣就再也不須要交換from區和to區,堆中任意地址空間均可以成爲from區或to區。

拷貝垃圾回收器的一個優勢是存活對象的位置會被to區中從新分配,緊湊存放,能夠徹底消除碎片化。碎片化是其餘垃圾回收算法所要面臨的一大問題,這點會在後續討論。

拷貝垃圾回收的缺陷

一般來講,拷貝垃圾回收器是「stop-the-world」式的,即在垃圾回收週期內,應用程序是被掛起的,沒法工做。在「stop-the-world」式的實現中,所須要拷貝的區域越大,對應用程序的性能所形成的影響也越大。對於那些很是注重響應時間的應用程序來講,這是難以接受的。使用拷貝垃圾回收時,你還須要考慮一下最壞狀況,即當from區中全部的對象都是存活對象的時候。所以,你不得不給存活對象預留出足夠的空間,也就是說to區必須足夠大,大到能夠將from區中全部的對象都放進去。正是因爲這個缺陷,拷貝垃圾回收算法在內存使用效率上略有不足。

標記-清理垃圾回收器

大多數部署在企業生產環境的商業JVM都使用了標記-清理(或標記)垃圾回收器,這種垃圾回收器並不會想拷貝垃圾回收器那樣對應用程序的性能有那麼大的影響。其中最著名的幾款是CMS、G一、GenPar和DeterministicGC(參見相關資源)。

標記-清理垃圾回收器會跟蹤引用,並使用標記位將每一個找到的對象標記位「live」。一般來講,每一個標記位都關聯着一個地址或堆上的一個地址集合。例如,標記位多是對象頭(object header)中一位,一個位向量,或是一個位圖。

當全部的存活對象都被標記位「live」後,將會開始*清理*階段。通常來講,垃圾回收器的清理階段包含了經過再次遍歷堆(不只僅是標記位live的對象集合,而是整個堆)來定位內存地址空間中未被標記的區域,並將其回收。而後,垃圾回收器會將這些被回收的區域保存到空閒列表(free list)中。在垃圾回收器中能夠同時存在多個空閒列表——一般會按照保存的內存塊的大小進行劃分。某些JVM(例如JRockit實時系統, JRockit Real Time System)在實現垃圾回收器時會給予應用程序分析數據和對象大小統計數據來動態調整空閒列表所保存的區域塊的大小範圍。

當清理階段結束後,應用程序就能夠再次啓動了。給新建立的對象分配內存時會從空閒列表中查找,而空閒列表中內存塊的大小須要匹配於新建立的對象大小、某個線程中平均對象大小,或應用程序所設置的TLAB的大小。從空閒列表中爲新建立的對象找到大小合適的內存區域塊有助於優化內存的使用,減小內存中的碎片。

關於TLAB
        更多關於TLAB和TLA(Thread Local Allocation Buffer和Thread Local Area)的內容,請參見ImportNew翻譯整理的第一篇《JVM性能優化, Part 1 ―― JVM簡介》。

標記-清理垃圾回收器的缺陷

標記階段的時長取決於堆中存活對象的總量,而清理階段的時長則依賴於堆的大小。因爲在*標記*階段和*清理*階段完成前,你無事可作,所以對於那些具備較大的堆和較多存活對象的應用程序來講,使用此算法須要想辦法解決暫停時間(pause-time)較長這個問題。

對於那些內存消耗較大的應用程序來講,你可使用一些GC調優選項來知足其在某些場景下的特殊需求。不少時候,調優至少能夠將標記-清理階段給應用程序或性能要求(SLA,SLA指定了應用程序須要達到的響應時間的要求,即延遲)所帶來的風險推後。當負載和應用程序發生改變後,須要從新調優,由於某次調優只對特定的工做負載和內存分配速率有效。

標記-清理算法的實現

目前,標記-清理垃圾回收算法至少已有2種商業實現,而且都已在生產環境中被證實有效。其一是並行垃圾回收,另外一個是併發(或多數時間併發)垃圾回收。

並行垃圾回收器

並行垃圾回收指的是垃圾回收是多線程並行完成的。大多數商業實現的並行垃圾回收器都是stop-the-world式的垃圾回收器,即在整個垃圾回收週期結束前,全部應用程序線程都會被掛起。掛起全部應用程序線程使垃圾回收器能夠以並行的方式,更有效的完成標記和清理工做。並行使得效率大大提升,一般能夠在像SPECjbb這樣的吞吐量基準測試中跑出高分。若是你的應用程序好似有限考慮吞吐量的,那麼並行垃圾回收是你最好的選擇。

對於大多數並行垃圾回收器來講,尤爲是考慮到應用於生產環境中,最大的問題是,像拷貝垃圾回收算法同樣,在垃圾回收週期內應用程序沒法工做。使用stop-the-world式的並行垃圾回收會對優先考慮響應時間的應用程序產生較大影響,尤爲是當你有大量的引用須要跟蹤,而此時剛好又有大量的、具備複雜結構的對象存活於堆中的時候,狀況將更加糟糕。(記住,標記-清理垃圾回收器回收內存的時間取決於跟蹤存活對象中全部引用的時間與遍歷整個堆的時間之和。)以並行方式執行垃圾回收所致使的應用程序暫停會一直持續到整個垃圾回收週期結束。

併發垃圾回收器

併發垃圾回收器更適用於那些對響應時間比較敏感的應用程序。併發指的是一些(或大多數)垃圾回收工做能夠與應用程序線程同時運行。因爲並不是全部的資源都由垃圾回收器使用,所以這裏所面臨的問題如何決定什麼時候開始執行垃圾回收,能夠保證垃圾回收順利完成。這裏須要足夠的時間來跟蹤存活對象即的引用,並在應用程序出現OOM錯誤前回收內存。若是垃圾回收器沒法及時完成,則應用程序就會拋出OOM錯誤。此外,一直作垃圾回收也很差,會沒必要要的消耗應用程序資源,從而影響應用程序吞吐量。要想在動態環境中保持這種平衡就須要一些技巧,所以設計了啓發式方法來決定什麼時候開始垃圾回收,什麼時候執行不一樣的垃圾回收優化任務,以及一次執行多少垃圾回收優化任務等。

併發垃圾回收器所面臨的另外一個挑戰是如何決定什麼時候執行一個須要完整堆快照的操做時安全的,例如,你須要知道是什麼時候標記全部存活對象的,這樣才能轉而進入清理階段。在大多數並行垃圾回收器採用的stop-the-world方式中,*階段轉換(phase-switching)*並不須要什麼技巧,由於世界已靜止(堆上對象暫時不會發生變化)。可是,在併發垃圾回收中,轉換階段時可能並非安全的。例如,若是應用程序修改了一塊垃圾回收器已經標記過的區域,可能會涉及到一些新的或未被標記的引用,而這些引用使其指向的對象成爲存活狀態。在某些併發垃圾回收的實現中,這種狀況有可能會使應用程序陷入長時間運行重標記(re-mark)的循環,所以當應用程序須要分配內存時沒法獲得足夠作的空閒內存。

到目前爲止的討論中,已經介紹了各類垃圾回收器和垃圾回收算法,他們各自適用於不一樣的場景,知足不一樣應用程序的需求。各類垃圾回收方式不只在算法上有所區別,在具體實現上也不盡相同。因此,在命令行中指定垃圾回收器以前,最好能瞭解應用程序的需求及其自身特色。在下一節中,將介紹Java平臺內存模型中的陷阱,在這裏,陷阱指的是在動態生產環境中,Java程序員經常作出的一些中使性能更糟,而非更好的假設。

爲何調優沒法取代垃圾回收

大多數Java程序員都知道,若是有很多方法能夠最大化Java程序的性能。而當今衆多的JVM實現,垃圾回收器實現,以及多到使人頭暈的調優選項均可能會讓開發人員將大量的時間消耗在無窮無盡的性能調優上。這種狀況催生了這樣一種結論,「GC是糟糕的,努力調優以下降GC的頻率或時長才是王道」。可是,真這麼作是有風險的。

考慮一下針對指定的應用程序需求作調優意味着什麼。大多數調優參數,如內存分配速率,對象大小,響應時間,以及對象死亡速度等,都是針對特定的狀況而來設定的,例如測試環境下的工做負載。例如。調優結果可能有如下兩種:

  1. 測試時正常,上線就失敗。

  2. 一旦應用程序自己,或工做負載發生改變,就須要所有重調。

調優是須要不斷往復的。使用併發垃圾回收器須要作不少調優工做,尤爲是在生產環境中。爲知足應用程序的需求,你須要不斷挑戰可能要面對的最差狀況。這樣作的結果就是,最終造成的配置很是刻板,並且在這個過程當中也浪費了大量的資源。這種調優方式(試圖經過調優來消除GC)是一種堂吉訶德式的探索——以根本不存在的理由去挑戰一個假想敵。而事實是,你針對某個特定的負載而垃圾回收器作的調優越多,你距離Java運行時的動態特性就越遠。畢竟,有多少應用程序的工做負載能保持不變呢?你所預估的工做負載的可靠性又有多高呢?

那麼,若是不從調優入手又該怎麼辦呢?有什麼其餘的辦法能夠防止應用程序出現OOM錯誤,並下降響應時間呢?這裏,首先要作的是明確影響Java應用程序性能的真正因素。

碎片化

影響Java應用程序性能的罪魁禍首並非垃圾回收器自己,而是碎片化,以及垃圾回收器如何處理碎片。碎片是Java堆中空閒空間,但因爲連續空間不夠大而沒法容納將要建立的對象。正如我在本系列第2篇中提到的,碎片多是TLAB中的剩餘空間,也多是(這種狀況比較多)被釋放掉的具備較長生命週期的小對象所佔用的空間。

隨着應用程序的運行,這種沒法使用的碎片會遍及於整個堆空間。在某些狀況下,這種狀態會因靜態調優選項(如提高速率和空閒列表等)更糟糕,以致於沒法知足應用程序的原定需求。這些剩下的空間(也就是碎片)沒法被應用程序有效利用起來。若是你對此聽任自流,就會致使不斷垃圾回收,垃圾回收器會不斷的釋放內存以便建立新對象時使用。在最差狀況下,甚至垃圾回收也沒法騰出足夠的內存空間(由於碎片太多),JVM會強制拋出OOM(out of memory)錯誤固然,你也能夠重啓應用程序來消除碎片,這樣可使Java堆面目一新,因而就又能夠爲對象分配內存了。可是,從新啓動會致使服務器停機,另外,一段時間以後,堆將再次充滿碎片,你也不得再也不次重啓。

OOM錯誤(OutOfMemoryErrors)會掛起進程,日誌中顯示的垃圾回收器很忙,是垃圾回收器努力釋放內存的標誌,也說明了堆中碎片很是多。一些開發人員經過從新調優垃圾回收器來解決碎片化的問題,但我覺着在解決碎片問題成爲垃圾回收的使命以前應該用一些更有新意的方法來解決這個問題。本文後面的內容將聚焦於能有效解決碎片化問題的方法:分代黛式垃圾回收和壓縮。

分代式垃圾回收

這個理論你能夠已經據說過,即在生產環境中,大部分對象的生命週期都很短。分代式垃圾回收就源於這個理論。在分代式垃圾回收中,堆被分爲兩個不一樣的空間(或成爲「代」),每一個空間存放具備不一樣年齡的對象,在這裏,年齡是指該對象所經歷的垃圾回收的次數(也就是該對象挺過了多少次垃圾回收而沒有死掉)。

當新建立的對象所處的空間,即*年輕代*,被對象填滿後,該空間中仍然存活的對象會被移動到老年代。(譯者注,以HotSpot爲例,這裏應該是挺過若干次GC而不死的,纔會被搬到老年代,而一些比較大的對象會直接放到老年代。)大多數的實現都將堆會分爲兩代,年輕代和老年代。一般來講,分代式垃圾回收器都是單向拷貝的,即從年輕代向老年代拷貝,這點在早先曾討論過。近幾年出現的年輕代垃圾回收器已經能夠實現並行垃圾回收,固然也能夠實現一些其餘的垃圾回收算法實現對年輕代和老年代的垃圾回收。若是你使用拷貝垃圾回收器(可能具備並行收集功能)對年輕代進行垃圾回收,那垃圾回收是stop-the-world式的(參見前面的解釋)。

分代式垃圾回收的缺陷

在分代式垃圾回收中,老年代執行垃圾回收的平率較低,而年輕代中較高,垃圾回收的時間較短,侵入性也較低。但在某些狀況下,年輕代的存在會是老年代的垃圾回收更加頻繁。典型的例子是,相比於Java堆的大小,年輕代被設置的太大,而應用程序中對象的生命週期又很長(又或者給年輕代對象提高速率設了一個「不正確」的值)。在這種狀況下,老年代因過小而放不下全部的存活對象,所以垃圾回收器就會忙於釋放內存以便存放從年輕代提高上來的對象。但通常來講,使用分代式垃圾回收器可使用應用程序的性能和系統延遲保持在一個合適的水平。

使用分代式垃圾回收器的一個額外效果是部分解決了碎片化的問題,或者說,發生最差狀況的時間被推遲了。可能形成碎片的小對象被分配於年輕代,也在年輕代被釋放掉。老年代中的對象分佈會相對緊湊一些,由於這些對象在從年輕代中提高上來的時候會被會緊湊存放。但隨着應用程序的運行,若是運行時間夠長的話,老年代也會充滿碎片的。這時就須要對年輕代和老年代執行一次或屢次stop-the-world式的全垃圾回收,致使JVM拋出_OOM錯誤_或者代表提高失敗的錯誤。但年輕代的存在使這種狀況的出現被推遲了,對某些應用程序來講,這就就足夠了。(在某些狀況下,這種糟糕狀況會被推遲到應用程序徹底不關心GC的時候。)對大多數應用程序來講,對於大多數使用年輕代做爲緩衝的應用程序來講,年輕代的存在能夠下降出現stop-the-world式垃圾回收頻率,減小拋出OOM錯誤的次數。

 調優分代式垃圾回收

正如上面提到的,因爲使用了分代式垃圾回收,你須要針對每一個新版本的應用程序和不一樣的工做負載來調全年輕代大小和對象提高速度。我沒法完整評估出固定運行時的代價:因爲針對某個指定工做負載而設置了一系列優化參數,垃圾回收器應對動態變化的能力下降了,而變化是不可避免的。

對於調全年輕代大小來講,最重要的規則是要確保年輕代的大小不該該使因執行stop-the-world式垃圾回收而致使的暫停過長。(假設年輕代中使用的並行垃圾回收器。)還要記住的是,你要在堆中爲老年代留出足夠的空間來存放那些生命週期較長的對象。下面還有一些在調優分代式垃圾回收器時須要考慮的因素:

  1. 大多數年輕代垃圾回收都是stop-the-world式的,年輕代越大,相應的暫停時間越長。因此,對於那些受GC暫停影響較大的應用程序來講,應該仔細斟酌年輕代的大小。

  2. 你能夠綜合考慮不一樣代的垃圾回收算法。能夠在年輕代使用並行垃圾回收,而在老年代使用並行垃圾回收。

  3. 當提高失敗頻繁發生時,這一般說明老年代中的碎片較多。提高失敗指的是老年代中沒有足夠大的空間來存放年輕代中的存活對象。當出現提示失敗時,你能夠微調對象提高速率(即調整對象提高時年齡),或者確保老年代垃圾回收算法會將對象進行壓縮(將在下一節討論),並以一種適合當前應用程序工做負載的方式調整壓縮。你也能夠增大堆和各個代的大小,但這會使老年代垃圾回收的暫停時間延長——記住,碎片化是不可避免的。

  4. 分代式垃圾回收最適用於那些具備大量短生命週期對象的應用程序,這些對象的生命週期短到活不過一次垃圾回收週期。在這種場景中,分代式垃圾回收可有效的減緩碎片化的趨勢,主要是將碎片化隨帶來的影響推出到未來,而那時可能應用程序對此絕不關心。

壓縮

儘管分代式垃圾回收推出了碎片化和OOM錯誤出現的時機,但壓縮仍然是惟一真正解決碎片化的方法。*壓縮*是將對象移動到一塊兒,以便釋放掉大塊連續內存空間的GC策略。所以,壓縮能夠生成足夠大的空間來存放新建立的對象。

移動對象並修改相關引用是一個stop-the-world式的操做,這會對應用程序的性能形成影響。(只有一種狀況是個例外,將在本系列的下一篇文章中討論。)存活對象越多,垃圾回收形成的暫停也越長。假如堆中的空間所剩無幾,並且碎片化又比較嚴重(這一般是因爲應用程序運行的時間很長了),那麼對一塊存活對象多的區域進行壓縮可能會耗費數秒的時間。而若是因出現OOM而致使應用程序沒法運行,所以而對整個堆進行壓縮時,所消耗的時間可達數十秒。

壓縮致使的暫停時間的長短取決於須要移動的存活對象所佔用的內存有多大以及有多少引用須要更新。當堆比較大時,從統計上講,存活對象和須要更新的引用都會不少。從已觀察到的數據看,每壓縮1到2GB存活數據的須要約1秒鐘。因此,對於4GB的堆來講,極可能會有至少25%的存活數據,從而致使約1秒鐘的暫停。

壓縮與應用程序內存牆

應用程序內存牆涉及到在GC暫停時間對應用程序的影響大到沒法達到知足預約需求以前所能設置的的堆的最大值。目前,大部分Java應用程序在碰到內存牆時,每一個JVM實例的堆大小介於4GB到20GB之間,具體數值依賴於具體的環境和應用程序自己。這也是大多數企業及應用程序會部署多個小堆JVM而不是部署少數大堆(50到60GB)JVM的緣由之一。在這裏,咱們須要思考一下:現代企業中有多少Java應用程序的設計與部署架構受制於JVM中的壓縮?在這種狀況下,咱們接受多個小實例的部署方案,以增長管理維護時間爲代價,繞開爲處理充滿碎片的堆而執行stop-the-world式垃圾回收所帶來的問題。考慮到現今的硬件性能和企業級Java應用程序中對內存愈來愈多的訪問要求,這種方案是在很是奇怪。爲何僅僅只能給每一個JVM實例設置這麼小的堆?併發壓縮是一種可選方法,它能夠下降內存牆帶來的影響,這將是本系列中下一篇文章的主題。
從已觀察到的數據看,每壓縮1到2GB存活數據的須要約1秒鐘。因此,對於4GB的堆來講,極可能會有至少25%的存活數據,從而致使約1秒鐘的暫停。

總結:回顧

本文對垃圾回收作了整體介紹,目的是爲了使你能瞭解垃圾回收的相關概念和基本知識。但願本文能激發你繼續深刻閱讀相關文章的興趣。這裏所介紹的大部份內容,它們。在下一篇文章中,我將介紹一些較新穎的概念,併發壓縮,目前只有Azul公司的Zing JVM實現了這一技術。併發壓縮是對GC技術的綜合運用,這些技術試圖從新構建Java內存模型,考慮當今內存容量與處理能力的不斷提高,這一點尤其重要。

如今,回顧一下本文中所介紹的關於垃圾回收的一些內容:

  1. 不一樣的垃圾回收算法的方式是爲知足不一樣的應用程序需求而設計。目前在商業環境中,應用最爲普遍的是引用跟蹤垃圾回收器。

  2. 並行垃圾回收器會並行使用可用資源執行垃圾回收任務。這種策略的經常使用實現是stop-the-world式垃圾回收器,使用全部可用系統資源快速完成垃圾回收任務。所以,並行垃圾回收能夠提供較高的吞吐量,但在垃圾回收的過程當中,全部應用程序線程都會被掛起,對延遲有較大影響。

  3. 併發垃圾回收器能夠與應用程序併發工做。使用併發垃圾回收器時要注意的是,確保在應用程序發生OOM錯誤以前完成垃圾回收。

  4. 分代式垃圾回收能夠推遲碎片化的出現,但並不能消除碎片化。它將堆分爲兩塊空間,一塊用於存放「年輕對象」,另外一塊用於存放從年輕代中存活下來的存活對象。對於那些使用了不少具備較短生命週期活不過幾回垃圾回收週期的Java應用程序來講,使用分代式垃圾回收是很是合適的。

  5. 壓縮是能夠徹底解決碎片化的惟一方法。大多數垃圾回收器在壓縮的時候是都stop-the-world式的。應用程序運行的時間越長,對象間的引就用越複雜,對象大小的異質性也越高。相應的,完成壓縮所須要的時間也越長。若是堆的大小較大的話也會對壓縮所佔產生的暫停有影響,由於較大的堆就會有更多的活動數據和更多的引用須要處理。

  6. 調優能夠推遲OOM錯誤的出現,但過分調優是無心義的。在經過試錯方式初始調優前,必定要明確生產環境負載的動態性,以及應用程序中的對象類型和對象間的引用狀況。在動態負載下,過於刻板的配置很容會失效。在設置非動態調優選項前必定要清楚這樣作後果。

下個月的_JVM性能調優系列_:深刻了解C4垃圾回收器(Continuously Concurrent Compacting Collector)相關算法。

關於做者

Eva Andearsson對JVM計數、SOA、雲計算和其餘企業級中間件解決方案有着10多年的從業經驗。在2001年,她以JRockit JVM開發者的身份加盟了創業公司Appeal Virtual Solutions(即BEA公司的前身)。在垃圾回收領域的研究和算法方面,EVA得到了兩項專利。此外她仍是提出了肯定性垃圾回收(Deterministic Garbage Collection),後來造成了JRockit實時系統(JRockit Real Time)。在技術上,Eva與Sun公司和Intel公司合做密切,涉及到不少將JRockit產品線、WebLogic和Coherence整合的項目。2009年,Eva加盟了Azul System公司,擔任產品經理。負責新的Zing Java平臺的開發工做。最近,她改換門庭,以高級產品經理的身份加盟Cloudera公司,負責管理Cloudera公司Hadoop分佈式系統,致力於高擴展性、分佈式數據處理框架的開發。

英文原文:JVM performance optimization, Part 3,翻譯:ImportNew - 曹旭東

譯文連接:http://www.importnew.com/2233.html

相關文章
相關標籤/搜索