jvm性能調優(轉載)

    網上看到一位javaeye的同志寫的文章,感受總結的比較好,雖然也研究過這些,但沒有系統的總結過,以爲是好文章,先收藏了,如今轉載下來!java

數據類型

    Java虛擬機中,數據類型能夠分爲兩類:基本類型引用類型 。基本類型的變量保存原始值,即:他表明的值就是數值自己;而引用類型的變量保存引用值。「引用值」表明了某個對象的引用,而不是對象自己,對象自己存放在這個引用值所表示的地址的位置。算法

基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress編程

引用類型包括:類類型接口類型數組windows

堆與棧

    堆和棧是程序運行的關鍵,頗有必要把他們的關係說清楚。數組


棧是運行時的單位,而堆是存儲的單位

    棧解決程序的運行問題,即程序如何執行,或者說如何處理數據;堆解決的是數據存儲的問題,即數據怎麼放、放在哪兒。緩存

    在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,由於不一樣的線程執行邏輯有所不一樣,所以須要一個獨立的線程棧。而堆則是全部線程共享 的。棧由於是運行單位,所以裏面存儲的信息都是跟當前線程(或程序)相關信息的。包括局部變量、程序運行狀態、方法返回值等等;而堆只負責存儲對象信息。ruby

    爲何要把堆和棧區分出來呢?棧中不是也能夠存儲數據嗎

    第一,從軟件設計的角度看,棧表明了處理邏輯 ,而堆表明了數據 。這樣分開,使得處理邏輯更爲清晰。分而治之的思想 。這種隔離、模塊化的思想在軟件設計的方方面面都有體現。多線程

    第二,堆與棧的分離,使得堆中的內容能夠被多個棧共享 (也能夠理解爲多個線程訪問同一個對象)。這種共享的收益是不少的。一方面這種共享提供了一種有效的數據交互方式(如:共享內存),另外一方面,堆中的共享常量和緩存能夠被全部棧訪問,節省了空間。併發

    第三,棧由於運行時的須要,好比保存系統運行的上下文,須要進行地址段的劃分。因爲棧只能向上增加,所以就會限制住棧存儲內容的能力。而堆不一樣,堆中的對象是能夠根據須要動態增加的,所以棧和堆的拆分,使得動態增加成爲可能 ,相應棧中只需記錄堆中的一個地址便可。jvm

    第四,面向對象就是堆和棧的完美結合 。其實, 面向對象方式的程序與之前結構化的程序在執行上沒有任何區別。可是,面向對象的引入,使得對待問題的思考方式發生了改變,而更接近於天然方式的思考。當我 們把對象拆開,你會發現,對象的屬性其實就是數據,存放在堆中;而對象的行爲(方法),就是運行邏輯,放在棧中。咱們在編寫對象的時候,其實即編寫了數據 結構,也編寫的處理數據的邏輯。不得不認可,面向對象的設計,確實很美。

    在Java中,Main函數就是棧的起始點,也是程序的起始點

    程序要運行老是有一個起點的。同C語言同樣,java中的Main就是那個起點。不管什麼java程序,找到main就找到了程序執行的入口:)

    堆中存什麼?棧中存什麼

    堆中存的是對象 。棧中存的是基本數據類型 和堆中對象的引用 。一個對象的大小是不可估計的,或者說是能夠動態變化的,可是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處:))。

    爲何不把基本類型放堆中呢?由於其佔用的空間通常是1~8個字節——須要空間比較少,並且由於是基本類型,因此不會出現動態增加的狀況——長度固定,因 此棧中存儲就夠了,若是把他存在堆中是沒有什麼意義的(還會浪費空間,後面說明)。能夠這麼說,基本類型和對象的引用都是存放在棧中,並且都是幾個字節的 一個數,所以在程序運行時,他們的處理方式是統一的。可是基本類型、對象引用和對象自己就有所區別了,由於一個是棧中的數據一個是堆中的數據。最多見的一 個問題就是,Java中參數傳遞時的問題。

    Java中的參數傳遞時傳值呢?仍是傳引用

    要說明這個問題,先要明確兩點:

         1. 不要試圖與C進行類比,Java中沒有指針的概念

         2. 程序運行永遠都是在棧中進行的,於是參數傳遞時,只存在傳遞基本類型和對象引用的問題 。不會直接傳對象自己。

    明確以上兩點後。Java在方法調用傳遞參數時,由於沒有指針,因此它都是進行傳值調用 (這點能夠參考C的傳值調用)。所以,不少書裏面都說Java是進行傳值調用,這點沒有問題,並且也簡化的C中複雜性。

    可是傳引用的錯覺是如何形成的呢? 在運行棧中,基本類型和引用的處理是同樣的,都是傳值 , 因此,若是是傳引用的方法調用,也同時能夠理解爲「傳引用值」的傳值調用,即引用的處理跟基本類型是徹底同樣的。可是當進入被調用方法時,被傳遞的這個引 用的值,被程序解釋(或者查找)到堆中的對象,這個時候纔對應到真正的對象。若是此時進行修改,修改的是引用對應的對象,而不是引用自己,即:修改的是堆 中的數據。因此這個修改是能夠保持的了。

    對象,從某種意義上說,是由基本類型組成的。能夠把一個對象看做爲一棵樹,對象的屬性若是仍是對象,則仍是一顆樹(即非葉子節點),基本類型則爲樹的葉子節點 。程序參數傳遞時,被傳遞的值自己都是不能進行修改的,可是,若是這個值是一個非葉子節點(即一個對象引用),則能夠修改這個節點下面的全部內容。

 

    堆和棧中,棧是程序運行最根本的東西。程序運行能夠沒有堆,可是不能沒有棧。而堆是爲棧進行數據存儲服務,說白了堆就是一塊共享的內存。不過,正是由於堆和棧的分離的思想,才使得Java的垃圾回收成爲可能。

     Java中,棧的大小經過-Xss來設置,當棧中存儲數據比較多時,須要適當調大這個值,不然會出現java.lang.StackOverflowError異常。常見的出現這個異常的是沒法返回的遞歸,由於此時棧中保存的信息都是方法返回的記錄點。

Java對象的大小

    基本數據的類型的大小是固定的,這裏就很少說了。對於非基本類型的Java對象,其大小就值得商榷。

    在Java中,一個空Object對象的大小是8byte ,這個大小隻是保存堆中一個沒有任何屬性的對象的大小。看下面語句:

Java代碼   收藏代碼
  1. Object ob = new Object();  

 這樣在程序中完成了一個Java對象的生命,可是它所佔的空間爲:4byte+8byte 。4byte是上面部分所說的Java棧中保存引用的所須要的空間。而那8byte則是Java堆中對象的信息。由於全部的Java非基本類型的對象都須要默認繼承Object對象,所以不論什麼樣的Java對象,其大小都必須是大於8byte。

   有了Object對象的大小,咱們就能夠計算其餘對象的大小了。

Java代碼   收藏代碼
  1. Class NewObject {  
  2.   
  3.     int count;  
  4.   
  5.     boolean flag;  
  6.   
  7.     Object ob;  
  8.   
  9. }  
  10.   
  11.     其大小爲:空對象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小 (4byte)=17byte。可是由於Java在對對象內存分配時都是以8的整數倍來分,所以大於17byte的最接近8的整數倍的是24,所以此對象的大小爲24byte。  

   這裏須要注意一下基本類型的包裝類型的大小 。由於這種包裝類型已經成爲對象了,所以須要把他們做爲對象來看待。包裝類型的大小至少是12byte(聲明一個空Object至少須要的空間),並且12byte沒有包含任何有效信息,同時,由於Java對象大小是8的整數倍,所以一個基本類型包裝類的大小至少是16byte 。這個內存佔用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內存佔用更是誇張(隨便想下就知道了)。所以,可能的話應儘可能少使用包裝類。在JDK5.0之後,由於加入了自動類型裝換,所以,Java虛擬機會在存儲方面進行相應的優化。

引用類型

    對象引用類型分爲強引用、軟引用、弱引用和虛引用

 

強引用: 就是咱們通常聲明對象是時虛擬機生成的引用,強引用環境下,垃圾回收時須要嚴格判斷當前對象是否被強引用,若是被強引用,則不會被垃圾回收

 

軟引用: 軟引用一 般被作爲緩存來使用。與強引用的區別是,軟引用在垃圾回收時,虛擬機會根據當前系統的剩餘內存來決定是否對軟引用進行回收。若是剩餘內存比較緊張,則虛擬 機會回收軟引用所引用的空間;若是剩餘內存相對富裕,則不會進行回收。換句話說,虛擬機在發生OutOfMemory時,確定是沒有軟引用存在的。

 

弱引用: 弱引用與軟引用相似,都是做爲緩存來使用。但與軟引用不一樣,弱引用在進行垃圾回收時,是必定會被回收掉的,所以其生命週期只存在於一個垃圾回收週期內。

 

    強引用不用說,咱們系統通常在使用時都是用的強引用。而「軟引用」和「弱引用」比較少見。他們通常被做爲緩存使用,並且通常是在內存大小比較受限的狀況下 作爲緩存。由於若是內存足夠大的話,能夠直接使用強引用做爲緩存便可,同時可控性更高。於是,他們常見的是被使用在桌面應用系統的緩存。

JVM調優總結(三)-基本垃圾回收算法

能夠從不一樣的的角度去劃分垃圾回收算法:

按照基本回收策略分

引用計數(Reference Counting):

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

標記-清除(Mark-Sweep):


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

 

複製(Copying):


 

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

 

標記-整理(Mark-Compact):


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

按分區對待的方式分

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

 

分代收集(Generational Collecting): 基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法(上述方式中的一個)進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

 

按系統線程分

串行收集: 串行收集使用單線程處理全部垃圾回收工做,由於無需多線程交互,實現容易,並且效率比較高。可是,其侷限性也比較明顯,即沒法使用多處理器的優點,因此此收集適合單處理器機器。固然,此收集器也能夠用在小數據量(100M左右)狀況下的多處理器機器上。

 

並行收集: 並行收集使用多線程處理垃圾回收工做,於是速度快,效率高。並且理論上CPU數目越多,越能體現出並行收集器的優點。

 

併發收集: 相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工做時,須要暫停整個運行環境,而只有垃圾回收程序在運行,所以,系統在垃圾回收時會有明顯的暫停,並且暫停時間會由於堆越大而越長。

分享到:
評論
1 樓 vaneng 2010-07-14   引用
Java代碼   收藏代碼
  1. Class NewObject {     
  2.     
  3.     int count;     
  4.     
  5.     boolean flag;     
  6.     
  7.     Object ob;     
  8.     
  9. }   
類裏面的基本類型沒有內存對齊麼?就像結構體那樣.
相關文章
相關標籤/搜索