<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    walterwing  
    日歷
    <2008年11月>
    2627282930311
    2345678
    9101112131415
    16171819202122
    23242526272829
    30123456
    統(tǒng)計(jì)
    • 隨筆 - 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)容


     


    一. 引子

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

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

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

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

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

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

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

    二. 垃圾回收算法

    1. 引用計(jì)數(shù)

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

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

    這個算法除了低效外,還有個致命的缺陷:如果對象之間存在循環(huán)引用,可能會出現(xiàn)“對象應(yīng)該被回收,但引用計(jì)數(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垃圾回收算法之一。顧名思義,這個算法需要先暫停程序的運(yùn)行(因此它不屬于后臺回收模式),然后將所有“活”的對象從當(dāng)前堆(堆A)復(fù)制到另一個堆(堆B),然后一次性回收整個堆A。

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

    該算法主要有三個缺點(diǎn):

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

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

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

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

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

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

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

    但該算法也有其自身的缺點(diǎn):

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

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

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


    三. 分代

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

    1. Young(Nursery),年輕代

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

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

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

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

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

    2. Old(Tenured),年老代

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

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

    3. Permanent,持久代

    裝載Class信息等基礎(chǔ)數(shù)據(jù),默認(rèn)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默認(rèn)在老人區(qū)68%的時(shí)候促發(fā))。

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

    而因?yàn)槌杀娟P(guān)系對Old的檢查回收頻率要低很多,同時(shí)對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的默認(rèn)值。策略為:

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

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

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

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

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

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

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


    五. 其他

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

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

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

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

    posted on 2008-11-02 22:43 This is Wing 閱讀(504) 評論(0)  編輯  收藏 所屬分類: Java基礎(chǔ)
     
    Copyright © This is Wing Powered by: 博客園 模板提供:滬江博客
    主站蜘蛛池模板: 国产成人免费高清激情视频| 日韩中文无码有码免费视频| 久久久久久久国产免费看| 在线观看亚洲专区| 精品国产_亚洲人成在线高清| 国产一级淫片免费播放| 狼群影院在线观看免费观看直播| 久久精品国产亚洲av天美18| 亚洲国产精久久久久久久| 国产成A人亚洲精V品无码| 亚洲成A人片在线观看无码不卡| 亚洲性猛交XXXX| 亚洲啪啪AV无码片| 国产AV无码专区亚洲A∨毛片| 久久精品亚洲综合| 国产免费私拍一区二区三区| 蜜臀91精品国产免费观看| 国产成人免费手机在线观看视频 | 成人国产网站v片免费观看| 亚洲明星合成图综合区在线| 亚洲欧洲国产经精品香蕉网| 色偷偷亚洲女人天堂观看欧| 亚洲精品无码成人| 美女被艹免费视频| a级片免费在线观看| 99re热精品视频国产免费| 久草免费在线观看视频| 成人免费毛片观看| 亚洲精品国产福利一二区| 亚洲热妇无码AV在线播放| 亚洲男人第一av网站| 亚洲香蕉在线观看| 小说区亚洲自拍另类| 久久最新免费视频| 1000部禁片黄的免费看| 处破痛哭A√18成年片免费| 亚洲精品综合久久| 国产亚洲人成网站在线观看不卡 | 国产精品无码免费视频二三区| 免费成人在线观看| 久久久久亚洲AV成人无码网站|