JVM原理

繼上篇介紹JVM的crash時的hs_err_pid文件,由於對JVM並不瞭解,看起來特別吃力,經大神點播,得先明白JVM的原理,才能看懂那些報錯文檔。蒐集資料以下。程序員

http://blog.csdn.net/ning109314/article/details/10411495/ 介紹的很好,看起來簡單易懂。算法

對本身影響大的內容摘錄以下:數據結構

1、JVM多線程

JVM工做原理和特色主要是指操做系統裝入JVM是經過jdk中Java.exe來完成,經過下面4步來完成JVM環境.併發

1.建立JVM裝載環境和配置jvm

2.裝載JVM.dll工具

3.初始化JVM.dll並掛界到JNIENV(JNI調用接口)實例性能

4.調用JNIEnv實例裝載並處理class類。優化

 

在咱們運行和調試Java程序的時候,常常會提到一個JVM的概念.JVM是Java程序運行的環境,可是他同時一個操做系統的一個應用程序一個進程,所以他也有他本身的運行的生命週期,也有本身的代碼和數據空間.spa

2、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對象,因爲其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次便可回收。

    試想,在不進行對象存活時間區分的狀況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,由於每次回收都須要遍歷全部存活對象,但實際上,對於生命週期長的對象而言,這種遍歷是沒有效果的,由於可能進行了不少次遍歷,可是他們依舊存在。所以,分代垃圾回收採用分治的思想,進行代的劃分,把不一樣生命週期的對象放在不一樣代上,不一樣代上採用最適合它的垃圾回收方式進行回收。

 

jvm分代

 

 

 

如圖所示:

    虛擬機中的共劃分爲三個代:年輕代(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的各域分配策略動態變化

 

分代垃圾回收流程1

 

 

 

 

分代垃圾回收流程2

 

分代垃圾回收流程3

 

 

分代垃圾回收流程

相關文章
相關標籤/搜索