walterwing  
          日歷
          <2008年11月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456
          統(tǒng)計
          • 隨筆 - 12
          • 文章 - 1
          • 評論 - 7
          • 引用 - 0

          導(dǎo)航

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

           

          本篇內(nèi)容主要轉(zhuǎn)載自http://blog.csdn.net/calvinxiu/archive/2007/05/18/1614473.aspx,作者“江南白衣”

          結(jié)合自身的學(xué)習(xí),加入了《Thinking in Java 3rd Edition》中的部份相關(guān)內(nèi)容


           


          一. 引子

          首先需要明確的一點是:Java中的所有對象(基本類型除外)都在堆上進行分配。

          然而,Java語言的速度并不比其他那些在堆棧上分配空間的語言慢,其原因就在于Java的垃圾回收機制對于對象的創(chuàng)建具有非常明顯的效果。

          我們可以把C++的對想像成一個院子,里面每個對象都負責(zé)管理自己的底盤。一段時間以后,對象可能被銷毀,但地盤必須被重用。

          而Java中的堆更像一個傳送帶:你每分配一個對象,它就往前移動一格。這意味著對象存儲空間的分配速度非常快。Java的“堆指針”只是簡單地移動到尚未分配的區(qū)域,其效率比得上C++在堆棧上分配空間的效率。當(dāng)然,實際過程中還存在諸如簿記工作的少量額外開銷,但不會有像查找空間這樣的大動作。

          當(dāng)然,Java中的堆并非完全像傳送帶那樣工作。要真是那樣的話,勢必會導(dǎo)致頻繁的內(nèi)存頁面調(diào)度(這將極大影響性能),并最終耗盡資源。

          其中的秘密在于垃圾回收器的介入。當(dāng)它工作時,將一面回收空間,一面使堆中的對象緊湊排列,這樣“堆指針”就可以很容易移動到更靠近傳送帶的開始處,也就盡量避免了頁面錯誤。

          Java通過垃圾回收期對對象重新排列,從而實現(xiàn)了一種高速的、有無限空間可分配的堆模型。

          二. 垃圾回收算法

          1. 引用計數(shù)

          首先介紹一種最直觀最簡單但卻相當(dāng)不實用(實際上也并沒有被JVM采用)的回收算法——“引用計數(shù)”。我們介紹它是為了讓大家對垃圾回收有個初步的概念,再通過與其他算法的對比,了解到其他算法的精華與優(yōu)越性。

          所謂“引用計數(shù)”,是指每個對象都有一格引用計數(shù)器,當(dāng)有引用連接至對象時,引用計數(shù)加1。當(dāng)引用離開作用域或被設(shè)置為null時,引用計數(shù)減1。雖然管理引用計數(shù)的開銷不大,但需要在整個生命周期中持續(xù)地開銷。垃圾回收器會在含有全部對象的列表上遍歷,當(dāng)發(fā)現(xiàn)某個對象的引用計數(shù)為0時,就釋放其占用的空間

          這個算法除了低效外,還有個致命的缺陷:如果對象之間存在循環(huán)引用,可能會出現(xiàn)“對象應(yīng)該被回收,但引用計數(shù)卻不為零”的情況。對垃圾回收器而言,定位這樣存在交互引用的對象組所需的工作量極大。

           

          2. 理論依據(jù)

          在正式介紹JVM中常用的幾種垃圾回收算法之前,我們先來看一下JVM判斷待回收對象的基本思想:對任何“活”的對象,一定能最終追溯到其存活在堆棧或靜態(tài)存儲區(qū)之中的引用。這個引用鏈條可能會穿過數(shù)個對象層次。由此,如果你從堆棧和靜態(tài)存儲區(qū)開始,遍歷所有引用,就能找到所有“活”的對象。

          即對于發(fā)現(xiàn)的每個引用,你必須追蹤它所引用的對象,然后是此對象包含的所有的引用,如此反復(fù)的執(zhí)行,直到“根源于堆棧和靜態(tài)存儲區(qū)的引用”所形成的網(wǎng)絡(luò)全部被訪問為止。你所訪問過的所有對象必須都是“活”的。

          注意,這就解決了“存在交互引用的整體對象”的問題,這些對象根本不會被發(fā)現(xiàn),因此也就被自動回收了。

          3. “停止——復(fù)制”算法

          “停止——復(fù)制”算法是本篇將要介紹的三種JVM垃圾回收算法之一。顧名思義,這個算法需要先暫停程序的運行(因此它不屬于后臺回收模式),然后將所有“活”的對象從當(dāng)前堆(堆A)復(fù)制到另一個堆(堆B),然后一次性回收整個堆A。

          該算法的優(yōu)點在于:當(dāng)對象被復(fù)制到新堆時,它們是一個挨著一個的,所以新堆保持緊湊隊列,然后就可以按照前述方法簡單、直接地分配新空間了。

          該算法主要有三個缺點:

          缺點1:需要兩個堆,然后需要在兩個堆之間來回倒騰,從而使得維護比實際需要多一倍的空間。
          缺點2:復(fù)制。當(dāng)程序進入穩(wěn)定狀態(tài)后,可能只會產(chǎn)生少量的垃圾,甚至沒有垃圾。盡管如此,該算法仍然會將所有內(nèi)存自一處復(fù)制到另外一處,這很浪費。
          缺點3:需要暫停程序的運行。當(dāng)需要操作的堆空間較大時,耗費的時間是很可觀的。

          4. “標(biāo)記——清掃”算法

          “標(biāo)記——清掃”算法主要適用于垃圾較少的情況。

          該算法同樣是要找出所有“活”的對象。每當(dāng)它找到一個“活”對象,就會給對象設(shè)一個標(biāo)記,這個過程中不會回收任何對象。只有全部標(biāo)記工作完成時,清楚動作才會開始。在清除過程中,再次遍歷整個內(nèi)存區(qū)域,把所有沒有標(biāo)記的對象進行回收處理。

          相對于“停止——復(fù)制”算法,“標(biāo)記——清掃”算法具有如下優(yōu)點:

          優(yōu)點1:支持用戶線程與垃圾收集線程并發(fā)執(zhí)行(后臺回收模式),一開始會很短暫的停止一次所有線程來開始初始標(biāo)記根對象,然后標(biāo)記線程與應(yīng)用線程與應(yīng)用線程一起并發(fā)運行,最后又很短的暫停一次,多線程并行地重新標(biāo)記之前可能因為并發(fā)而漏掉的對象,然后就開始與應(yīng)用程序的并發(fā)清除過程。可見,最長的兩個遍歷過程都是與應(yīng)用程序并發(fā)執(zhí)行的,比“停止——復(fù)制”算法改進很多

          優(yōu)點2:當(dāng)垃圾較少時,運行效率要比“停止——復(fù)制”方法高很多

          但該算法也有其自身的缺點:

          缺點:在清除過程中,釋放沒有被標(biāo)記的對象,導(dǎo)致剩下的堆空間不是連續(xù)的,產(chǎn)生很多碎片。

          5. “標(biāo)記——整理”算法

          綜合了上述兩種的做法和優(yōu)點,先標(biāo)記活躍對象,然后將其合并成較大的內(nèi)存塊


          三. 分代

          分代是Java垃圾收集的一大亮點,根據(jù)對象的生命周期長短,把堆分為3個代:Young,Old和Permanent,根據(jù)不同代的特點采用不同的收集算法,揚長避短也。

          1. Young(Nursery),年輕代

          研究表明大部分對象都是朝生暮死,隨生隨滅的。因此所有收集器都為年輕代選擇了復(fù)制算法。

          復(fù)制算法優(yōu)點是只訪問活躍對象,缺點是復(fù)制成本高。因為年輕代只有少量的對象能熬到垃圾收集,因此只需少量的復(fù)制成本。而且復(fù)制收集器只訪問活躍對象,對那些占了最大比率的死對象視而不見,充分發(fā)揮了它遍歷空間成本低的優(yōu)點。

          Young的默認值為4M,隨堆內(nèi)存增大,約為1/15,JVM會根據(jù)情況動態(tài)管理其大小變化。

          -XX:NewRatio= 參數(shù)可以設(shè)置Young與Old的大小比例,-server時默認為1:2,但實際上young啟動時遠低于這個比率?如果信不過JVM,也可以用-Xmn硬性規(guī)定其大小,有文檔推薦設(shè)為Heap總大小的1/4。

          Young里面又分為3個區(qū)域,一個Eden,所有新建對象都會存在于該區(qū),兩個Survivor區(qū),用來實施復(fù)制算法。每次復(fù)制就是將Eden和第一塊Survior的活對象復(fù)制到第2塊,然后清空Eden與第一塊Survior。Eden與Survivor的比例由-XX:SurvivorRatio=設(shè)置,默認為32。Survivio大了會浪費,小了的話,會使一些年輕對象潛逃到老人區(qū),引起老人區(qū)的不安,但這個參數(shù)對性能并不重要。

          2. Old(Tenured),年老代

          年輕代的對象如果能夠挺過數(shù)次收集,就會進入老人區(qū)。老人區(qū)使用標(biāo)記整理算法。因為老人區(qū)的對象都沒那么容易死的,采用復(fù)制算法就要反復(fù)的復(fù)制對象,很不合算,只好采用標(biāo)記清理算法,但標(biāo)記清理算法其實也不輕松,每次都要遍歷區(qū)域內(nèi)所有對象,所以還是沒有免費的午餐啊。

          -XX:MaxTenuringThreshold=設(shè)置熬過年輕代多少次收集后移入老人區(qū),CMS中默認為0,熬過第一次GC就轉(zhuǎn)入,可以用-XX:+PrintTenuringDistribution查看。

          3. Permanent,持久代

          裝載Class信息等基礎(chǔ)數(shù)據(jù),默認64M,如果是類很多很多的服務(wù)程序,需要加大其設(shè)置-XX:MaxPermSize=,否則它滿了之后會引起fullgc()或Out of Memory。 注意Spring,Hibernate這類喜歡AOP動態(tài)生成類的框架需要更多的持久代內(nèi)存

          4. minor/major collection

          每個代滿了之后都會促發(fā)collection,(另外Concurrent Low Pause Collector默認在老人區(qū)68%的時候促發(fā))。

          GC用較高的頻率對young進行掃描和回收,這種叫做minor collection

          而因為成本關(guān)系對Old的檢查回收頻率要低很多,同時對Young和Old的收集稱為major collection
             
          System.gc()會引發(fā)major collection,使用-XX:+DisableExplicitGC禁止它,或設(shè)為CMS并發(fā)-XX:+ExplicitGCInvokesConcurrent

          5. 小結(jié)

          Young  -- 復(fù)制算法

          Old(Tenured)  -- 標(biāo)記清除/標(biāo)記整理算法

          四. 收集器

          1.古老的串行收集器(Serial Collector)

          使用 -XX:+UseSerialGC,策略為年輕代串行復(fù)制,年老代串行標(biāo)記整理。

          2.吞吐量優(yōu)先的并行收集器(Throughput Collector)

          使用 -XX:+UseParallelGC ,也是JDK5 -server的默認值。策略為:

              1).年輕代暫停應(yīng)用程序,多個垃圾收集線程并行的復(fù)制收集,線程數(shù)默認為CPU個數(shù),CPU很多時,可用–XX:ParallelGCThreads=減少線程數(shù)。
              2).年老代暫停應(yīng)用程序,與串行收集器一樣,單垃圾收集線程標(biāo)記整理。

             
          所以需要2+的CPU時才會優(yōu)于串行收集器,適用于后臺處理,科學(xué)計算。

          可以使用-XX:MaxGCPauseMillis= 和 -XX:GCTimeRatio 來調(diào)整GC的時間。

          3.暫停時間優(yōu)先的并發(fā)收集器(Concurrent Low Pause Collector-CMS)

          使用-XX:+UseConcMarkSweepGC,策略為:

              1).年輕代同樣是暫停應(yīng)用程序,多個垃圾收集線程并行的復(fù)制收集。
              2).年老代則只有兩次短暫停,其他時間應(yīng)用程序與收集線程并發(fā)的清除。

          注意并行與并發(fā)的區(qū)別:并行指多條垃圾收集線程并行;并發(fā)指用戶線程與垃圾收集線程并發(fā),程序在繼續(xù)運行,而垃圾收集程序運行于另一個個CPU上


          五. 其他

          Java虛擬機中有許多附加技術(shù)用以提升速度。尤其是與加載器操作有關(guān)的,被稱為“即時”(Just-In-Time,JIT)編譯的技術(shù)。這種技術(shù)可以把程序全部或部份翻譯成本地機器碼(這本來是Java虛擬機的工作),程序運行速度因此得以提升。

          當(dāng)需要裝載某個類(通常是在你為該類創(chuàng)建第一個對象)時,編譯器會先找到其.class文件,然后將該類的字節(jié)碼裝入內(nèi)存。此時,有兩種方案可供選擇:

          一種是就讓即使編譯器編譯所有代碼。但這個做法有兩個缺陷:這種加載動作散落在整個生命周期內(nèi),累加起來要花更多時間;并且會增加可執(zhí)行代碼的長度(字節(jié)碼要比即時編譯器展開后的本地機器碼小很多),這將導(dǎo)致頁面調(diào)度,從而降低程序速度。

          另一種做法稱為“惰性編譯(lazy evaluation)”,意思是即使編譯器只在必要的時候才編譯代碼。這樣,從不會被執(zhí)行的代碼也許壓根就不會被JIT所編譯。JDK 1.4中的Java HotSpot技術(shù)就采用了類似方法,代碼每次被執(zhí)行的時候都會做一些優(yōu)化,所以執(zhí)行的次數(shù)越多,它的速度越快。

          posted on 2008-11-02 22:43 This is Wing 閱讀(508) 評論(0)  編輯  收藏 所屬分類: Java基礎(chǔ)
           
          Copyright © This is Wing Powered by: 博客園 模板提供:滬江博客
          主站蜘蛛池模板: 七台河市| 澄迈县| 石门县| 巢湖市| 乐东| 阿合奇县| 紫阳县| 腾冲县| 宜昌市| 莱阳市| 和田县| 宣恩县| 蛟河市| 芷江| 内乡县| 景宁| 临江市| 甘孜县| 临漳县| 桂林市| 曲沃县| 邵武市| 泸溪县| 阆中市| 定安县| 嵩明县| 浦北县| 丰都县| 平度市| 宣恩县| 青田县| 密山市| 秀山| 红河县| 仙桃市| 修水县| 青田县| 比如县| 眉山市| 红桥区| 道孚县|