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

          導航

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

           

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

          結合自身的學習,加入了《Thinking in Java 3rd Edition》中的部份相關內容


           


          一. 引子

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

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

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

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

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

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

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

          二. 垃圾回收算法

          1. 引用計數

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

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

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

           

          2. 理論依據

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

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

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

          3. “停止——復制”算法

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

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

          該算法主要有三個缺點:

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

          4. “標記——清掃”算法

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

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

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

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

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

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

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

          5. “標記——整理”算法

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


          三. 分代

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

          1. Young(Nursery),年輕代

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

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

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

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

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

          2. Old(Tenured),年老代

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

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

          3. Permanent,持久代

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

          4. minor/major collection

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

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

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

          5. 小結

          Young  -- 復制算法

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

          四. 收集器

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

          使用 -XX:+UseSerialGC,策略為年輕代串行復制,年老代串行標記整理。

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

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

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

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

          可以使用-XX:MaxGCPauseMillis= 和 -XX:GCTimeRatio 來調整GC的時間。

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

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

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

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


          五. 其他

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

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

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

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

          posted on 2008-11-02 22:43 This is Wing 閱讀(507) 評論(0)  編輯  收藏 所屬分類: Java基礎
           
          Copyright © This is Wing Powered by: 博客園 模板提供:滬江博客
          主站蜘蛛池模板: 西乌珠穆沁旗| 油尖旺区| 兴化市| 蕉岭县| 榆树市| 平原县| 久治县| 中西区| 海晏县| 贺兰县| 衡东县| 应城市| 桐柏县| 二连浩特市| 锡林浩特市| 涟源市| 梨树县| 会东县| 磐石市| 施秉县| 视频| 平湖市| 云龙县| 延寿县| 尼木县| 五台县| 瑞安市| 阳新县| 江川县| 阿克陶县| 大同市| 彰武县| 庐江县| 金溪县| 龙门县| 台州市| 东兰县| 沁阳市| 藁城市| 威信县| 上饶县|