日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

GC 基础

發布時間:2025/5/22 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GC 基础 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

= GC 基礎 =====================


JAVA堆的描述如下:


內存由 Perm 和 Heap 組成. 其中
Heap = {Old + NEW = { Eden , from, to } }
JVM內存模型中分兩大塊,一塊是 NEW Generation, 另一塊是Old Generation. 在New Generation中,有一個叫Eden的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收后存活下來的對象。在Old Generation中,主要存放應用程序中生命周期長的內存對象,還有個Permanent Generation,主要用來放JVM自己的反射對象,比如類對象和方法對象等。
垃圾回收描述:
在New Generation塊中,垃圾回收一般用Copying的算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個Survivor Space, 當Survivor Space空間滿了后, 剩下的live對象就被直接拷貝到Old Generation中去。因此,每次GC后,Eden內存塊會被清空。在Old Generation塊中,垃圾回收一般用mark-compact的算法,速度慢些,但減少內存要求.
垃圾回收分多級,0級為全部(Full)的垃圾回收,會回收OLD段中的垃圾;1級或以上為部分垃圾回收,只會回收NEW中的垃圾,內存溢出通常發生于OLD段或Perm段垃圾回收后,仍然無內存空間容納新的Java對象的情況。


當一個URL被訪問時,內存申請過程如下:
A. JVM會試圖為相關Java對象在Eden中初始化一塊內存區域
B. 當Eden空間足夠時,內存申請結束。否則到下一步
C. JVM試圖釋放在Eden中所有不活躍的對象(這屬于1或更高級的垃圾回收), 釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
D. Survivor區被用來作為Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區
E. 當OLD區空間不夠時,JVM會在OLD區進行完全的垃圾收集(0級)
F. 完全垃圾收集后,若Survivor及OLD區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現”out of memory錯誤”
JVM調優建議:
ms/mx:定義YOUNG+OLD段的總尺寸,ms為JVM啟動時YOUNG+OLD的內存大小;mx為最大可占用的YOUNG+OLD內存大小。在用戶生產環境上一般將這兩個值設為相同,以減少運行期間系統在內存申請上所花的開銷。
NewSize/MaxNewSize:定義YOUNG段的尺寸,NewSize為JVM啟動時YOUNG的內存大小;MaxNewSize為最大可占用的YOUNG內存大小。在用戶生產環境上一般將這兩個值設為相同,以減少運行期間系統在內存申請上所花的開銷。
PermSize/MaxPermSize:定義Perm段的尺寸,PermSize為JVM啟動時Perm的內存大小;MaxPermSize為最大可占用的Perm內存大小。在用戶生產環境上一般將這兩個值設為相同,以減少運行期間系統在內存申請上所花的開銷。
SurvivorRatio:設置Survivor空間和Eden空間的比例
內存溢出的可能性


1. OLD段溢出
這種內存溢出是最常見的情況之一,產生的原因可能是:
1) 設置的內存參數過小(ms/mx, NewSize/MaxNewSize)
2) 程序問題
單個程序持續進行消耗內存的處理,如循環幾千次的字符串處理,對字符串處理應建議使用StringBuffer。此時不會報內存溢出錯,卻會使系統持續垃 圾收集,無法處理其它請求,相關問題程序可通過Thread Dump獲取(見系統問題診斷一章)單個程序所申請內存過大,有的程序會申請幾十乃至幾百兆內存,此時JVM也會因無法申請到資源而出現內存溢出,對此首 先要找到相關功能,然后交予程序員修改,要找到相關程序,必須在Apache日志中尋找。
當Java對象使用完畢后,其所引用的對象卻沒有銷毀,使得JVM認為他還是活躍的對象而不進行回收,這樣累計占用了大量內存而無法釋放。由于目前市面上還沒有對系統影響小的內存分析工具,故此時只能和程序員一起定位。
2. Perm段溢出
通常由于Perm段裝載了大量的Servlet類而導致溢出,目前的解決辦法:
1) 將PermSize擴大,一般256M能夠滿足要求
2) 若別無選擇,則只能將servlet的路徑加到CLASSPATH中,但一般不建議這么處理


3. C Heap溢出
系統對C Heap沒有限制,故C Heap發生問題時,Java進程所占內存會持續增長,直到占用所有可用系統內存
其他:
JVM有2個GC線程。第一個線程負責回收Heap的Young區。第二個線程在Heap不足時,遍歷Heap,將Young 區升級為Older區。Older區的大小等于-Xmx減去-Xmn,不能將-Xms的值設的過大,因為第二個線程被迫運行會降低JVM的性能。
為什么一些程序頻繁發生GC?有如下原因:
l ? ? ? ? 程序內調用了System.gc()或Runtime.gc()。
l ? ? ? ? 一些中間件軟件調用自己的GC方法,此時需要設置參數禁止這些GC。
l ? ? ? ? Java的Heap太小,一般默認的Heap值都很小。
l ? ? ? ? 頻繁實例化對象,Release對象。此時盡量保存并重用對象,例如使用StringBuffer()和String()。
如果你發現每次GC后,Heap的剩余空間會是總空間的50%,這表示你的Heap處于健康狀態。許多Server端的Java程序每次GC后最好能有65%的剩余空間。
經驗之談:
1.Server端JVM最好將-Xms和-Xmx設為相同值。為了優化GC,最好讓-Xmn值約等于-Xmx的1/3[2]。
2.一個GUI程序最好是每10到20秒間運行一次GC,每次在半秒之內完成[2]。
注意:
1.增加Heap的大小雖然會降低GC的頻率,但也增加了每次GC的時間。并且GC運行時,所有的用戶線程將暫停,也就是GC期間,Java應用程序不做任何工作。
2.Heap大小并不決定進程的內存使用量。進程的內存使用量要大于-Xmx定義的值,因為Java為其他任務分配內存,例如每個線程的Stack等。
2.Stack的設定
每個線程都有他自己的Stack。

-Xss 每個線程的Stack大小

?

Stack的大小限制著線程的數量。如果Stack過大就好導致內存溢漏。-Xss參數決定Stack大小,例如-Xss1024K。如果Stack太小,也會導致Stack溢漏。
3.硬件環境
硬件環境也影響GC的效率,例如機器的種類,內存,swap空間,和CPU的數量。
如果你的程序需要頻繁創建很多transient對象,會導致JVM頻繁GC。這種情況你可以增加機器的內存,來減少Swap空間的使用[2]。
4.4種GC
第一種為單線程GC,也是默認的GC。,該GC適用于單CPU機器。
第二種為Throughput GC,是多線程的GC,適用于多CPU,使用大量線程的程序。第二種GC與第一種GC相似,不同在于GC在收集Young區是多線程的,但在Old區和第一種一樣,仍然采用單線程。-XX:+UseParallelGC參數啟動該GC。
第三種為Concurrent Low Pause GC,類似于第一種,適用于多CPU,并要求縮短因GC造成程序停滯的時間。這種GC可以在Old區的回收同時,運行應用程序。-XX:+UseConcMarkSweepGC參數啟動該GC。
第四種為Incremental Low Pause GC,適用于要求縮短因GC造成程序停滯的時間。這種GC可以在Young區回收的同時,回收一部分Old區對象。-Xincgc參數啟動該GC。


按照基本回收策略分

引用計數(Reference Counting):

比較古老的回收算法。原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

?

標記-清除(Mark-Sweep):

?

?

?

此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

?

復制(Copying):

?

?

?

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

?

標記-整理(Mark-Compact):

?

?

?

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


?

按分區對待的方式分

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

?

分代收集(Generational Collecting):基于對對象生命周期分析后得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命周期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

?

按系統線程分

串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其局限性也比較明顯,即無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。

?

并行收集:并行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出并行收集器的優勢。(串型收集的并發版本,需要暫停jvm) 并行paralise指的是多個任務在多個cpu中一起并行執行,最后將結果合并。效率是N倍。

?

并發收集:相對于串行收集和并行收集而言,前面 兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。 (和并行收集不同,并發只有在開頭和結尾會暫停jvm)并發concurrent指的是多個任務在一個cpu偽同步執行,但其實是串行調度的,效率并非直 接是N倍。

?

分代垃圾回收

??? 分代的垃圾回收策略,是基于這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。

?

?? ?在Java程序運行的過程中,會產生大量的對象,其中有些對象是與業務信息相關,比如Http請求中的Session對象、線程、Socket連接,這 類對象跟業務直接掛鉤,因此生命周期比較長。但是還有一些對象,主要是程序運行過程中生成的臨時變量,這些對象生命周期會比較短,比如:String對 象,由于其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次即可回收。

?

?? ?試想,在不進行對象存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,因為每次回收都需要遍歷所有存活對象,但實 際上,對于生命周期長的對象而言,這種遍歷是沒有效果的,因為可能進行了很多次遍歷,但是他們依舊存在。因此,分代垃圾回收采用分治的思想,進行代的劃 分,把不同生命周期的對象放在不同代上,不同代上采用最適合它的垃圾回收方式進行回收。

?

?

如圖所示:

?

?? ?虛擬機中的共劃分為三個代:年輕代(Young Generation)、年老點(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關系不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

?

?

年輕代:

?? ?所有新生成的對象首先都是放在年輕代的。年輕代的目標就是盡可能快速的收集掉那些生命周期短的對象。年輕代分三個區。一個Eden區,兩個 Survivor區(一般而言)。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當這個 Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當這個Survivor區也滿了的時候,從第一個Survivor區復制 過來的并且此時還存活的對象,將被復制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時 存在從Eden復制過來 對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空 的。同時,根據程序需要,Survivor區是可以配置為多個的(多于兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。

?

年老代:

?? ?在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。

?

持久代:

?? ?用于存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate 等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進行設 置。

?

什么情況下觸發垃圾回收?

由于對象進行了分代處理,因此垃圾回收區域、時間也不一樣。GC有兩種類型:Scavenge GCFull GC

?

Scavenge GC

?? ?一般情況下,當新對象生成,并且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區。然后整理Survivor的兩個區。這種方式的GC是對 年輕代的Eden區進行,不會影響到年老代。因為大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因 而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。

?

Full GC

?? ?對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個對進行回收,所以比Scavenge GC要慢,因此應該盡可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對于FullGC的調節。有如下原因可能導致Full GC:

· 年老代(Tenured)被寫滿

· 持久代(Perm)被寫滿?

· System.gc()被顯示調用?

·上一次GC之后Heap的各域分配策略動態變化

?

?

?

?

?

?

?

?

?

?

?

?

= G1 ===================================

傳說中的G1,傳說中的low-pause垃圾收集。Java SE 6的update14版本中已經包含測試版,可以在啟動時加JVM參數來啟用

-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC


http://www.blogjava.net/BlueDavy/archive/2009/03/11/259230.html

本文摘自《構建高性能的大型分布式Java應用》一 書,Garbage First簡稱G1,它的目標是要做到盡量減少GC所導致的應用暫停的時間,讓應用達到準實時的效果,同時保持JVM堆空間的利用率,將作為CMS的替代 者在JDK 7中閃亮登場,其最大的特色在于允許指定在某個時間段內GC所導致的應用暫停的時間最大為多少,例如在100秒內最多允許GC導致的應用暫停時間為1秒, 這個特性對于準實時響應的系統而言非常的吸引人,這樣就再也不用擔心系統突然會暫停個兩三秒了。

G1要做到這樣的效果,也是有前提的,一方面是硬件環境的要求,必須是多核的CPU以及較大的內存(從規范來看,512M以上就滿足條件了),另外一方面是需要接受吞吐量的稍微降低,對于實時性要求高的系統而言,這點應該是可以接受的。

為了能夠達到這樣的效果,G1在原有的各種GC策略上進行了吸收和改進,在G1中可以看到增量收集器和CMS的影子,但它不僅僅是吸收原有GC策略的優 點,并在此基礎上做出了很多的改進,簡單來說,G1吸收了增量GC以及CMS的精髓,將整個jvm Heap劃分為多個固定大小的region,掃描時采用Snapshot-at-the-beginning的并發marking算法(具體在后面內容詳 細解釋)對整個heap中的region進行mark,回收時根據region中活躍對象的bytes進行排序,首先回收活躍對象bytes小以及回收耗 時短(預估出來的時間)的region,回收的方法為將此region中的活躍對象復制到另外的region中,根據指定的GC所能占用的時間來估算能回 收多少region,這點和以前版本的Full GC時得處理整個heap非常不同,這樣就做到了能夠盡量短時間的暫停應用,又能回收內存,由于這種策略在回收時首先回收的是垃圾對象所占空間最多的 region,因此稱為Garbage First。

看完上面對于G1策略的簡短描述,并不能清楚的掌握G1,在繼續詳細看G1的步驟之前,必須先明白G1對于JVM Heap的改造,這些對于習慣了劃分為new generation、old generation的大家來說都有不少的新意。

G1將Heap劃分為多個固定大小的region,這也是G1能夠實現控制GC導致的應用暫停時間的前提,region之間的對象引用通過 remembered set來維護,每個region都有一個remembered set,remembered set中包含了引用當前region中對象的region的對象的pointer,由于同時應用也會造成這些region中對象的引用關系不斷的發生改 變,G1采用了Card Table來用于應用通知region修改remembered sets,Card Table由多個512字節的Card構成,這些Card在Card Table中以1個字節來標識,每個應用的線程都有一個關聯的remembered set log,用于緩存和順序化線程運行時造成的對于card的修改,另外,還有一個全局的filled RS buffers,當應用線程執行時修改了card后,如果造成的改變僅為同一region中的對象之間的關聯,則不記錄remembered set log,如造成的改變為跨region中的對象的關聯,則記錄到線程的remembered set log,如線程的remembered set log滿了,則放入全局的filled RS buffers中,線程自身則重新創建一個新的remembered set log,remembered set本身也是一個由一堆cards構成的哈希表。

盡管G1將Heap劃分為了多個region,但其默認采用的仍然是分代的方式,只是僅簡單的劃分為了年輕代(young)和非年輕代,這也是由于G1仍 然堅信大多數新創建的對象都是不需要長的生命周期的,對于應用新創建的對象,G1將其放入標識為young的region中,對于這些region,并不 記錄remembered set logs,掃描時只需掃描活躍的對象,G1在分代的方式上還可更細的劃分為:fully young或partially young,fully young方式暫停的時候僅處理young regions,partially同樣處理所有的young regions,但它還會根據允許的GC的暫停時間來決定是否要加入其他的非young regions,G1是運行到fully-young方式還是partially young方式,外部是不能決定的,在啟動時,G1采用的為fully-young方式,當G1完成一次Concurrent Marking后,則切換為partially young方式,隨后G1跟蹤每次回收的效率,如果回收fully-young中的regions已經可以滿足內存需要的話,那么就切換回fully young方式,但當heap size的大小接近滿的情況下,G1會切換到partially young方式,以保證能提供足夠的內存空間給應用使用。

除了分代方式的劃分外,G1還支持另外一種pure G1的方式,也就是不進行代的劃分,pure方式和分代方式的具體不同在下面的具體執行步驟中進行描述。

掌握了這些概念后,繼續來看G1的具體執行步驟:

1.?????????Initial Marking

G1對于每個region都保存了兩個標識用的bitmap,一個為previous marking bitmap,一個為next marking bitmap,bitmap中包含了一個bit的地址信息來指向對象的起始點。

開始Initial Marking之前,首先并發的清空next marking bitmap,然后停止所有應用線程,并掃描標識出每個region中root可直接訪問到的對象,將region中top的值放入next top at mark start(TAMS)中,之后恢復所有應用線程。

觸發這個步驟執行的條件為:

l??G1定義了一個JVM Heap大小的百分比的閥值,稱為h,另外還有一個H,H的值為(1-h)*Heap Size,目前這個h的值是固定的,后續G1也許會將其改為動態的,根據jvm的運行情況來動態的調整,在分代方式下,G1還定義了一個u以及soft limit,soft limit的值為H-u*Heap Size,當Heap中使用的內存超過了soft limit值時,就會在一次clean up執行完畢后在應用允許的GC暫停時間范圍內盡快的執行此步驟;

l??在pure方式下,G1將marking與clean up組成一個環,以便clean up能充分的使用marking的信息,當clean up開始回收時,首先回收能夠帶來最多內存空間的regions,當經過多次的clean up,回收到沒多少空間的regions時,G1重新初始化一個新的marking與clean up構成的環。

2.?????????Concurrent Marking

按照之前Initial Marking掃描到的對象進行遍歷,以識別這些對象的下層對象的活躍狀態,對于在此期間應用線程并發修改的對象的以來關系則記錄到remembered set logs中,新創建的對象則放入比top值更高的地址區間中,這些新創建的對象默認狀態即為活躍的,同時修改top值。

3.?????????Final Marking Pause

當應用線程的remembered set logs未滿時,是不會放入filled RS buffers中的,在這樣的情況下,這些remebered set logs中記錄的card的修改就會被更新了,因此需要這一步,這一步要做的就是把應用線程中存在的remembered set logs的內容進行處理,并相應的修改remembered sets,這一步需要暫停應用,并行的運行。

4.?????????Live Data Counting and Cleanup

值得注意的是,在G1中,并不是說Final Marking Pause執行完了,就肯定執行Cleanup這步的,由于這步需要暫停應用,G1為了能夠達到準實時的要求,需要根據用戶指定的最大的GC造成的暫停時 間來合理的規劃什么時候執行Cleanup,另外還有幾種情況也是會觸發這個步驟的執行的:

l??G1采用的是復制方法來進行收集,必須保證每次的”to space”的空間都是夠的,因此G1采取的策略是當已經使用的內存空間達到了H時,就執行Cleanup這個步驟;

l??對于full-young和partially-young的分代模式的G1而言,則還有情況會觸發Cleanup的執行,full-young模 式下,G1根據應用可接受的暫停時間、回收young regions需要消耗的時間來估算出一個yound regions的數量值,當JVM中分配對象的young regions的數量達到此值時,Cleanup就會執行;partially-young模式下,則會盡量頻繁的在應用可接受的暫停時間范圍內執行 Cleanup,并最大限度的去執行non-young regions的Cleanup。

這一步中GC線程并行的掃描所有region,計算每個region中低于next TAMS值中marked data的大小,然后根據應用所期望的GC的短延時以及G1對于region回收所需的耗時的預估,排序region,將其中活躍的對象復制到其他 region中。

?

G1為了能夠盡量的做到準實時的響應,例如估算暫停時間的算法、對于經常被引用的對象的特殊 處理等,G1為了能夠讓GC既能夠充分的回收內存,又能夠盡量少的導致應用的暫停,可謂費盡心思,從G1的論文中的性能評測來看效果也是不錯的,不過如果 G1能允許開發人員在編寫代碼時指定哪些對象是不用mark的就更完美了,這對于有巨大緩存的應用而言,會有很大的幫助,G1將隨JDK 6 Update 14?beta發布。


轉載于:https://www.cnblogs.com/wuxiang/p/3947753.html

總結

以上是生活随笔為你收集整理的GC 基础的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。