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

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

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

    posts - 78,  comments - 48,  trackbacks - 0
    概要

    這篇文章,是PRO JAVA EE 5 Performance Management and Optimization 的一個章節(jié),作者Steven Haines分享了他在調(diào)優(yōu)企業(yè)級JAVA應(yīng)用時所遇到的常見問題。

    Java EE(Java企業(yè)開發(fā)平臺)應(yīng)用程序,無論應(yīng)用程序服務(wù)器如何部署,所面對的一系列問題大致相同。作為一個JAVAEE問題解決專家,我曾經(jīng)面對過眾多的環(huán)境同時也寫了不少常見問題的觀察報告。在這方面,我覺得我很象一個汽車修理工人:你告訴修理工人發(fā)動機有聲音,他就會詢問你一系列的問題,幫你回憶發(fā)動機運行的情形。從這些信息中,他尋找到可能引起問題的原因。

    眾多解決問題的方法思路基本相同,第一天我同要解決問題的客戶接觸,接觸的時候,我會尋找已經(jīng)出現(xiàn)的問題以及造成的負(fù)面的影響。了解應(yīng)用程序的體系結(jié)構(gòu)和問題表現(xiàn)出的癥狀,這些工作很夠很大程度上提高我解決問題的幾率。在這一節(jié),我分享我在這個領(lǐng)域遇過的常見問題和他們的癥狀。希望這篇文章能成為你JAVAEE的故障檢測手冊。

    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時請務(wù)必保留以下作者信息和鏈接
    作者:Steven Haines;tsr106
    原文:http://www.javaworld.com/javaworld/jw-06-2006/jw-0619-tuning.html
    Matrix:http://www.matrix.org.cn/resource/article/44/44575_JAVAEE+Performance+Tuning.html
    關(guān)鍵字:VTJAVAEE;Performance Tuning

    內(nèi)存溢出錯誤

    最常見的折磨著企業(yè)級應(yīng)用程序的錯誤是讓人恐懼的outofmemoryError(內(nèi)存溢出錯誤)
    這個錯誤引起下面這些典型的癥狀:
    ----應(yīng)用服務(wù)器崩潰
    ----性能下降
    ----一個看起來好像無法結(jié)束的死循環(huán)在重復(fù)不斷的執(zhí)行垃圾收集,它會導(dǎo)致程序停止運行,并且經(jīng)常導(dǎo)致應(yīng)用服務(wù)器崩潰
    不管癥狀是什么,如果你想讓程序恢復(fù)正常運行,你一般都需要重新啟動應(yīng)用服務(wù)器。

    引發(fā)out-of-memory 錯誤的原因
    在你打算解決out-of-memory 錯誤之前,首先了解為什么會引發(fā)這個錯誤對你有很大的幫助。如果JVM里運行的程序, 它的內(nèi)存堆和持久存儲區(qū)域的都滿了,這個時候程序還想創(chuàng)建對象實例的話,垃圾收集器就會啟動,試圖釋放足夠的內(nèi)存來創(chuàng)建這個對象。這個時候如果垃圾收集器沒有能力釋放出足夠的內(nèi)存,它就會拋出OutOfMemoryError內(nèi)存溢出錯誤。

    Out-of-memory錯誤一般是JAVA內(nèi)存泄漏引起的。回憶上面所討論的內(nèi)容,內(nèi)存泄漏的原因是一個對象雖然不被使用了,但是依然還有對象引用他。當(dāng)一個對象不再被使用時,但是依然有一個或多個對象引用這個對象,因此垃圾收集器就不會釋放它所占據(jù)的內(nèi)存。這塊內(nèi)存就被占用了,堆中也就少了塊可用的空間。在WEB REQUESTS中這種類型的的內(nèi)存泄漏很典型,一兩個內(nèi)存對象的泄漏可能不會導(dǎo)致程序服務(wù)器的崩潰,但是10000或者20000個就可能會導(dǎo)致這個惡果。而且,大多數(shù)這些泄漏的對象并不是象DOUBLE或者INTEGER這樣的簡單對象,而可能是存在于堆中一系列相關(guān)的對象。例如,你可能在不經(jīng)意間引用了一個Person對象,但是這個對象包含一個Profile對象,此對象還包含了許多擁有一系列數(shù)據(jù)的PerformanceReview對象。這樣不只是丟失了那個Person對象所占據(jù)的100 bytes的內(nèi)存,你丟失了這一系列相關(guān)對象所占據(jù)的內(nèi)存空間,可能是高達(dá)500KB甚至更多。

    為了尋找這個問題的真正根源,你需要判斷是內(nèi)存泄漏還是以O(shè)utOfMemoryError形式出現(xiàn)的其他一些故障。我使用以下2種方法來判斷:
    ----深入分析內(nèi)存數(shù)據(jù)
    ----觀察堆的增長方式

    不同JVM(JAVA虛擬機)的調(diào)整程序的運作方式是不相同的,例如SUN和IBM的JVM,但都有相同的的地方。

    SUN JVM的內(nèi)存管理方式
    SUN的JVM是類似人類家族,也就是在一個地方創(chuàng)建對象,在它長期占據(jù)空間之前給它多次死亡的機會。
    SUN JVM會劃分為:
    1????????年輕的一代(Young generation),包括EDEN和2個幸存者空間(出發(fā)地和目的地the From space and the To space)
    2????????老一代(Old generation)
    3????????永久的一代(Permanent generation)

    圖1?? 解釋了SUN 堆的家族和空間的詳細(xì)分類
    image

    對象在EDEN出生就是被創(chuàng)建,當(dāng)EDEN滿了的時候,垃圾收集器就把所有在EDEN中的對象掃描一次,把所有有效的對象拷貝到第一個幸存者空間,同時把無效的對象所占用的空間釋放。當(dāng)EDEN再次變滿了的時候,就啟動移動程序把EDEN中有效的對象拷貝到第二個幸存者空間,同時,也將第一個幸存者空間中的有效對象拷貝到第二個幸存者空間。如果填充到第二個生存者空間中的有效對象被第一個生存者空間或EDEN中的對象引用,那么這些對象就是長期存在的(也就是說,他們被拷貝到老一代)。若垃圾收集器依據(jù)這種小幅度的調(diào)整收集(minor collection)不能找出足夠的空間,就是象這樣的拷貝收集(copy collection),就運行大幅度的收集,就是讓所有的東西停止(stop-the-world collection)。運行這個大幅度的調(diào)整收集時,垃圾收集器就停止所有在堆中運行的線程并執(zhí)行清除動作(mark-and-sweep collection),把新一代空間釋放空并準(zhǔn)備重啟程序。

    圖2和圖3展示的是了小幅度收集如何運行

    image
    圖2。對象在EDEN被創(chuàng)建一直到這個空間變滿。

    image
    圖3。處理的順序十分重要:垃圾收集器首先掃描EDEN和生存者空間,這就保證了占據(jù)空間的對象有足夠的機會證明自己是有效的。


    圖4展示了一個小幅度調(diào)整是如何運行的
    image
    圖4:當(dāng)垃圾收集器釋放所有的無效的對象并把有效的對象移動到一個更緊湊整齊的新空間,它將EDEN和生存者空間清空。

    以上就是SUN實現(xiàn)的垃圾收集器機制,你可以看出在老一代中的對象會被大幅度調(diào)整器收集清除。長生命周期的對象的清除花費的代價很高,因此如果你希望生命周期短的對象在占據(jù)空間前及時的死亡,就需要一個主垃圾收集器去回收他們的內(nèi)存。

    上面所講解的東西是為了更好的幫助我們識別出內(nèi)存泄漏。當(dāng)JAVA中的一個對象包含了一個并不想要的一個指向其他對象的引用的時候,內(nèi)存就會泄漏,這個引用阻止了垃圾收集器去回收它所占據(jù)的內(nèi)存。采用這種機制的SUN 虛擬機,對象不會被丟棄,而是利用自己特有的方法把他們從樂園和幸存者空間移動到老一代地區(qū)。因此,在一個基于多用戶的WEB環(huán)境,如果許多請求造成了泄漏,你就會發(fā)現(xiàn)老一代的增長。

    圖5顯示了那些潛在可能造成泄漏的對象:主收集器收集后遺留下來占據(jù)空間的對象會越來越多。不是所有的占據(jù)空間的對象都造成內(nèi)存泄漏,但是造成內(nèi)存泄漏的對象最終都占據(jù)者空間。如果內(nèi)存泄漏的確存在,這些造成泄漏的對象就會不斷的占據(jù)空間,直至造成內(nèi)存溢出。

    因此,我們需要去跟蹤垃圾收集器在處理老一代中的運行:每次垃圾收集器大幅度收集運行時,有多少內(nèi)存被釋放?老一代內(nèi)容是不是按一定的原理來增長?

    image
    圖5。陰影表示在經(jīng)過大幅度的收集后幸存下來的對象,這些對象是潛在可能引發(fā)內(nèi)存泄漏的對象

    一部分這些相關(guān)的信息是可以通過跟蹤API得到,更詳細(xì)的信息通過詳細(xì)的垃圾收集器的日志也可以看到。和所有的跟蹤技術(shù)一樣,日值記錄詳細(xì)的程度影響著JVM的性能,你想得到的信息越詳細(xì),付出的代價也就越高。為了能夠判斷內(nèi)存是否泄漏,我使用了能夠顯示輩分之間所有的不同的較權(quán)威的技術(shù)來顯示他們的區(qū)別,并以此來得到結(jié)果。SUN 的日志報告提供的信息比這個詳細(xì)的程度超過5%,我的很多客戶都一直使用那些設(shè)置來保證他們管理和調(diào)整垃圾收集器。下面的這個設(shè)置能夠給你提供足夠的分析數(shù)據(jù):
    –verbose:gc –xloggc:gc.log –XX:+PrintGCDetails –XX:+PrintGCTimeStamps

    明確發(fā)現(xiàn)在整個堆中存在有潛在可能泄漏內(nèi)存的情況,用老一代增長的速率才比較有說服力。切記調(diào)查不能決定些什么:為了能夠最終確定你內(nèi)存泄漏,你需要離線在內(nèi)存模擬器中運行你的應(yīng)用程序。

    IBM JVM內(nèi)存管理模式
    IBM的JVM的機制有一點不同。它不是運行在一個巨大的繼承HEAP中,它僅在一個單一的地區(qū)維護(hù)了所有的對象同時隨著堆的增長來釋放內(nèi)存。這個堆是這樣運行的:在一開始運行的時候,它會很小,隨著對象實例不斷的填充,在需要執(zhí)行垃圾收集的地方清除掉無效的對象同時把所有有效的對象緊湊的放置到堆的底部。因此你可能猜測到了,如果想尋找可能發(fā)生的內(nèi)存泄漏應(yīng)該觀察堆中所有的動作,堆的使用率是在提高?

    如何分析內(nèi)存泄漏
    內(nèi)存泄漏非常難確定,如果你能夠確定是請求導(dǎo)致的,那你的工作就非常簡單了。把你的程序放入到運行環(huán)境中,并在內(nèi)存模擬器中運行,按下面的步驟來:
    1.????????在內(nèi)存模擬器中運行你的應(yīng)用程序
    2.????????執(zhí)行使用方案(制造請求)以便讓程序在內(nèi)存中裝載請求所需要的所有的對象,這可以為以后詳細(xì)的分析排除不必要的干擾
    3.????????在執(zhí)行使用方案前對堆進(jìn)行拍照以便捕獲其中所有運行的對象。
    4.????????再次運行使用方案。
    5.????????再次拍照,來捕獲使用方案運行之后堆中所有對象的狀態(tài)。
    6.????????比較這2個快照,找出執(zhí)行使用方案后本不應(yīng)該出現(xiàn)在堆中的對象。

    這個時候,你需要去和開發(fā)者交流,告訴他你所碰到的棘手的請求,他們可以判斷究竟是對象泄漏還是為了某個目的特地讓對象保留下來的。如果執(zhí)行完后并沒有發(fā)現(xiàn)內(nèi)存泄漏的情況,我一般會轉(zhuǎn)到步驟4再進(jìn)行多次類似的跟蹤。比如,我可能會將我的請求反復(fù)運行17次,希望我的泄漏分析能夠得到17個情況(或更多)。這個方法不一定總有用,但如果是因為請求引起的對象泄漏的話,就會有很大的幫助。

    如果你無法明確的判斷泄漏是因為請求引發(fā)的,你有2個選擇:
    1.????????模擬每一個被懷疑的請求直至發(fā)現(xiàn)內(nèi)存泄漏
    2.????????存配置一個內(nèi)存性能跟蹤工具

    第一個選項在小應(yīng)用程序中是確實可用的或者你非常走運的解決了問題,但對大型應(yīng)用程序不太有用。如果你有跟蹤工具的話第二個選擇是比較有用的。這些工具利用字節(jié)流工具跟蹤對象的創(chuàng)建和銷毀的數(shù)量,他們可以報告特定類中的對象的數(shù)量狀態(tài),例如把Collections類作為特定的請求。例如,一個跟蹤工具可以跟蹤/action/login.do請求,并在它完成后將其中的100個對象放入HASHMAP中。這個報告并不能告訴你造成泄漏的是代碼還是某個對象,而是告訴你在內(nèi)存模擬器中應(yīng)該留意那些類型的請求。把程序服務(wù)器放到產(chǎn)品環(huán)境中并不會使他們變敏感,而是跟蹤性能的工具可以使你的工作變的更簡單化。

    虛假內(nèi)存泄漏
    少數(shù)的一些問題看起來是內(nèi)存泄漏實際上并非如此。
    我將這些情況稱為假泄漏,表現(xiàn)在下面幾種情況:
    1.????????分析過早
    2.????????Session泄漏
    3.????????異常的持久區(qū)域

    這章節(jié)對這些假泄漏都進(jìn)行了調(diào)查,描述了如何去判斷這些情況以及如何處理.

    不要過早分析
    為了在尋找內(nèi)存泄漏的時候盡量減少出現(xiàn)判斷錯誤的可能性,你應(yīng)當(dāng)在適當(dāng)?shù)臅r候分析堆。危險是:一些生命周期長的對象需要裝載到堆中,因此在堆達(dá)到穩(wěn)定狀態(tài)且包含了核心對象之前具有很大的欺騙性。在分析堆之前,應(yīng)該讓應(yīng)用程序達(dá)到穩(wěn)定狀態(tài)。
    為了判斷是否過早的對堆進(jìn)行分析,持續(xù)2個小時對跟蹤到的分析快照進(jìn)行分析,看堆的使用率是上升還是下降。如果是下降,保存這個時候的內(nèi)存記錄。如果是上升,這個時候就需要分析內(nèi)存中的SESSION了。

    發(fā)生泄漏的session
    WEB請求經(jīng)常導(dǎo)致內(nèi)存泄漏,在一個WEB請求中,對象會被限制存儲在有限的幾個區(qū)域。這些區(qū)域就是:
    1.????????頁面區(qū)域
    2.????????請求區(qū)域
    3.????????上下文區(qū)域
    4.????????應(yīng)用程序區(qū)域
    5.????????靜態(tài)變量
    6.????????長生命周期的變量,例如SERVLET

    當(dāng)實現(xiàn)一些JSP(JAVASERVER頁面)時,在頁面上聲明的變量在頁面結(jié)束的時候就被釋放,這些變量僅僅在這個單獨的頁面存在時存在。WEB服務(wù)器會向應(yīng)用程序服務(wù)器傳送一系列參數(shù)和屬性,也就是在SERVLET和JSP之間傳輸HttpServletRequest中的對象。你的動態(tài)頁面依靠HttpServletRequest在不同的組件之間傳輸信息,但當(dāng)請求完成或者socket結(jié)束的時候,SERVLET控制器會釋放所有在HttpServletRequest 中的對象。這些對象僅在他們的請求的生命周期內(nèi)存在。

    HTTP是無狀態(tài)的,這意味著客戶向服務(wù)器發(fā)送一個請求,服務(wù)器回應(yīng)這個請求,這個傳遞就完成了,就是會話結(jié)束了。我們應(yīng)該感激WEB頁面幫我們做的日志,這樣我們就能向購物車放置東西,并去檢查它,服務(wù)器能夠定義一個跨越多請求的擴展對話。屬性和參數(shù)被放在各自用戶的HttpSession對象中,并通過它讓程序的SERVLET和JSP交流。利用這種辦法,頁面存儲你的信息并把他們添加到HttpSession中,因此你可以用購物車購買東西,并檢查商品和使用信用卡付帳。作為一個無狀態(tài)的協(xié)議,它總是客戶端發(fā)起連接請求,服務(wù)器需要知道一個會話存在多長時間,到時候就應(yīng)該釋放這個用戶的數(shù)據(jù)。超過這個會話的最長時間就是會話超時,他們在程序服務(wù)器中設(shè)置。除非明確的要求釋放對象或者這個會話失效,否則在會話超時之前會話中的對象會一直存在。

    正如session是為每個用戶管理對象一樣,ServletContext為整個程序管理對象。ServletContext的有效范圍是整個程序,因此你可以利用Servlet中的ServletContext或者JSP應(yīng)用程序?qū)ο笤谒械腟ervlet和JSP之間讓在這個程序中的所有用戶共享數(shù)據(jù)。ServletContext是最主要的存放程序配置信息和緩存程序數(shù)據(jù)的地方,例如JNDI的信息。

    如果數(shù)據(jù)不是存儲這個四個地方(頁面范圍,請求范圍,會話范圍,程序范圍)那就可能存儲在下面的對象中:
    1.????????靜態(tài)變量
    2.????????長生命周期的類變量

    每個類的靜態(tài)變量被JVM(JAVA虛擬機)所控制,他們存在與否和類是否已經(jīng)被初始化無關(guān)。一個類的所有實例共用一個存儲靜態(tài)變量的地方,因此在任何一個實例中修改靜態(tài)變量會影響這個類的其他實例。因此,如果一個程序在靜態(tài)變量中存放了一個對象,如果這個變量生命周期沒有到,那么這個對象就不會被JVM釋放。這些靜態(tài)對象是造成內(nèi)存泄漏的主要原因。

    最后,對象能夠被放到內(nèi)部數(shù)據(jù)類型或者長生命周期類中的成員變量中,例如SERVLET。當(dāng)一個SERVLET被創(chuàng)建并且被裝載到內(nèi)存,它在內(nèi)存中僅有一個實例,采用多線程去訪問這個SERVLET實例。如果在INIT()方法中裝載配置信息,將他存儲于類變量中,那么當(dāng)需要維護(hù)的時候就可以隨時讀出這些信息,這樣所有的對象就用相同的配置。我常碰到的一個問題就是利用SERVLET類變量去存儲象頁面緩存這樣的信息。在他們自己內(nèi)部本身存貯這些緩存配置是個不錯的選擇,但存貯在SERVLET中是最糟糕的情況。如果你需要使用緩存,你最好使用第三方控制插件,例如 TANGOSOL的COHERENCE。

    當(dāng)在頁面或者請求范圍中利用變量存放對象的時候,在他們結(jié)束的時候這些對象會自動釋放。同樣,在SESSION中存放對象的時候,當(dāng)程序明確說明此SESSION失效的或者會話執(zhí)行超時的時候,這些對象才會自動被釋放。

    很多看起來象內(nèi)存泄漏的情況都是上面的那些會話中的泄漏。一個造成泄漏的會話并不是泄漏了內(nèi)存而是類似于泄漏,它消耗了內(nèi)存,但最終這些內(nèi)存都會被釋放的。如果程序服務(wù)器發(fā)生內(nèi)存溢出,判斷是內(nèi)存泄漏還是內(nèi)存缺乏的最好的方法就是:停止所有向這個服務(wù)器所發(fā)的請求的對象,等待會話超時,看內(nèi)存時候會被釋放出來。這雖然不會一定能夠達(dá)到你要的目的,但是這是最好的分段處理方法,當(dāng)你裝載測試器的時候,你應(yīng)該先掛斷你內(nèi)容巨大的會話而不是先去尋找內(nèi)存泄漏。

    通常來說,如果你執(zhí)行了一個很大的會話,你應(yīng)該盡量去減少它所占用的內(nèi)存空間,如果可以的話最好能重構(gòu)程序,以減少session所占據(jù)的內(nèi)存空間。下面2種方法可以降低大會話和內(nèi)存的沖突:
    1.????????增大堆的空間以支持你的大會話
    2.????????縮短會話的超時時間,讓它能夠快速的失效

    一個巨大的堆會導(dǎo)致垃圾回收花費更多的時間,因此這不是一個好解決方法,但總比發(fā)生OutofMemoryError強。增加足夠的堆空間以使它能夠存儲所有應(yīng)該保存的有效值,也意味著你必須有足夠的內(nèi)存去存儲所有訪問你站點的用戶的有效會話。如果商業(yè)規(guī)則允許的話最好能縮短會話超時的時間,以減少堆占用空間的沖突。
    總結(jié)下,你應(yīng)該依據(jù)合理性和重要性按下面的步驟依次去執(zhí)行:
    1.????????重構(gòu)程序,盡量減少擁有session范圍的變量所存儲的信息量
    2.????????鼓勵你的客戶在他們使用完后,明確的釋放會話
    3.????????縮短超時的時間,以便于讓你內(nèi)存盡快的得到回收
    4.????????增加你堆空間的大小

    無論如何,不要讓程序范圍級的變量,靜態(tài)變量,長生命周期的類存儲對象,事實上,你需要在內(nèi)存模擬器中去分析泄漏。
    異常的持久空間

    容易誤解JVM為持久空間分配內(nèi)存的目的。堆僅僅存儲類的實例,但JVM在堆中創(chuàng)建類實例之前,它必須把字節(jié)流文件(.class文件)裝載到程序內(nèi)存中。它利用內(nèi)存中的字節(jié)流在堆中創(chuàng)建類的實例。JVM利用程序的內(nèi)存來裝載字節(jié)流文件,這個內(nèi)存空間稱為持久空間。圖6顯示了持久空間和堆的關(guān)系:它存在于JVM程序中,并不是堆的一部分。

    image
    Figure 6. The relationship between the permanent space and the heap

    通常,你可能想讓你的持久空間足夠大以便于它能夠裝載你程序所有的類,因為很明顯,從文件系統(tǒng)中讀取類文件比從內(nèi)存中裝載代價高很多。JVM提供了一個參數(shù)讓你不的程序不卸載已經(jīng)裝載到持久空間中的類文件:
    –noclassgc

    這個參數(shù)選項告訴JVM不要跑到持久空間去執(zhí)行垃圾收集釋放其中已經(jīng)裝載的類文件。這個參數(shù)選項很聰明,但是會引起一個問題:當(dāng)持久空間滿了以后依然需要裝載新文件的時候JVM會怎么處理呢?我觀測到的資料說明:如果JVM檢測到持久空間還需要內(nèi)存,就會調(diào)用主垃圾收集程序。垃圾收集器清除堆,但它并不會對持久空間進(jìn)行任何操作,因此它的努力是白費的。于是JVM就再重新檢測持久空間,看它是否滿,然后再次執(zhí)行程序,一遍的一遍重復(fù)。

    我第一次碰到這種問題的時候,用戶抱怨說程序性能很差勁,并且在運行了幾次后就出現(xiàn)了問題,可能是內(nèi)存溢出問題。在我調(diào)查了詳細(xì)的關(guān)于堆和程序內(nèi)存利用的收集器的記錄后,我迅速發(fā)覺堆的狀態(tài)非常正常,但程序確發(fā)生了內(nèi)存溢出。這個用戶維持了數(shù)千的JSP頁面,在裝載到內(nèi)存前把他們都編譯成了字節(jié)流文件放入持久空間。他的環(huán)境已經(jīng)造成了持久空間溢出,但是在堆中由于用了 -noclassgc 選項,于是JVM并不去釋放類文件來裝載新的類文件。于是就導(dǎo)致了內(nèi)存溢出錯誤,我把他的持久空間改為512M大小,并去掉了 -noclassgc 參數(shù)。

    正像圖7顯示的,當(dāng)持久空間變滿了的時候,就引發(fā)垃圾收集,清理了樂園和幸存者空間,但是并不釋放持久空間中的一點內(nèi)存。

    image
    Figure 7. Garbage collection behavior when the permanent space becomes full. Click on thumbnail to view full-sized image.??????????
    ????????????????????????
    注意
    當(dāng)設(shè)置持久空間大小時候,一般考慮128M,除非你的程序有很多的類文件,這個時候,你就可以考慮使用256M大小。如果你想讓他能夠裝載所有的類的時候,就會導(dǎo)致一個典型的結(jié)構(gòu)錯誤。設(shè)置成512M就足夠了,它僅僅是暫時的時間的花費。把持久空間設(shè)置成512M大小就象給一個腳痛的人吃止痛藥,雖然暫時緩解了痛,但是腳還是沒有好,依然需要醫(yī)生把痛治療好,否則只是把問題延遲了而已。

    線程池

    外界同WEB或程序服務(wù)器連接的主要方法就是向他們發(fā)送請求,這些請求被放置到程序的執(zhí)行次序隊列中。和內(nèi)存最大的沖突就是程序服務(wù)器所設(shè)置的線程池的大小。線程池的大小就是程序可以同時處理的請求的數(shù)量。如果池太小,請求就需要在隊列中等待程序處理,如果太大,CPU就需要花費太多的時間在這些眾多的線程之間來回的切換。
    每個服務(wù)器都有一個SOCKET負(fù)責(zé)監(jiān)聽。程序把接受到的請求放到待執(zhí)行隊列中,然后將這個請求從隊列移動到線程中被程序處理。

    圖8顯示了服務(wù)器的處理程序。

    image
    Figure 8. 服務(wù)器處理請求的次序結(jié)構(gòu)

    線程池太小
    每當(dāng)我碰到有人抱怨裝載速度的性能隨著裝載的數(shù)量的增加變的越來越糟糕的時候,我會首先檢查線程池。特別是,我在看到下面這些信息的時候:
    1.線程池的使用
    2.很多請求等待處理(在隊列中等待處理)

    當(dāng)一個線程池被待處理的請求裝滿的時候,響應(yīng)的時間就變的極其糟糕,因為這些在隊列中等待處理的請求會消耗很多的額外時間。這個時候,CPU的利用率會非常低,因為程序服務(wù)器沒有時間去指揮CPU工作。這個時候,我會按一定幅度增加調(diào)節(jié)池的大小,并在未處理請求的數(shù)量減少前一直監(jiān)視程序的吞吐量,你需要一個合理甚至更好的負(fù)載量者,一個精確的負(fù)載量測試工具可以準(zhǔn)確的幫你測試出結(jié)果。當(dāng)你觀測吞吐量的時候,如果你發(fā)現(xiàn)吞吐量降低了,你就應(yīng)該把池的大小下調(diào)一個幅度,一直到找到讓它保持最大吞吐量的大小為止。

    圖9顯示了連接池太小的情況

    image
    Figure 9. 所有的線程都被占用了,請求就只能在隊列中等待

    每當(dāng)我閱讀性能調(diào)整手冊的時候,最讓我頭疼的就是他們從來不告訴你特殊情況下線程池應(yīng)該是多大。由于這些值非常依賴程序的行為,他們只告訴你大普通情況下正確的大小,但是他們給了你一個范圍內(nèi)的值,這對用戶很有利的。例如考慮下面2種情況::
    1.????????一個程序從內(nèi)存中讀出一個字符串,把它傳給JSP頁面,讓JSP頁面去顯示
    2.????????另一個程序從數(shù)據(jù)庫中讀出1000個數(shù)值,為這些不規(guī)則的數(shù)值求平均。第一個程序?qū)φ埱蟮幕貞?yīng)會很塊,大概僅需要不足0.25秒的時間,且不怎么占據(jù)CPU。第二個程序可能需要3秒去回應(yīng),同時會占據(jù)CPU。因此,為第一個程序配置的池大小是100就有點太小了,因為程序能夠同時處理200個;但為第二個程序配置的池是100,就有點太大了,因為CPU可能就能應(yīng)付50個線程。
    但是,很多程序并沒有在這種情況下動態(tài)的去調(diào)整的功能。多數(shù)情況下是做相同的事,但是應(yīng)該為他們劃分范圍。因此,我建議你為一個CPU分配50到75個左右的線程。對一些程序來說,這個數(shù)量可能太少,對另一個些來說可能太多,我剛開始為每個CPU分配50到75個線程,然后根據(jù)吞吐量和CPU的性能,并做適當(dāng)?shù)恼{(diào)整。

    線程池太大
    除了線程池數(shù)量太小之外的情況外,環(huán)境也可能把線程數(shù)量配置的過大。當(dāng)這些環(huán)境中的負(fù)載量不斷增大的時候,CPU的使用率會持續(xù)無法降低,就沒有什么響應(yīng)請求的時間了,因為CPU只顧的在眾多的線程之間來回的切換跳動,沒時間讓線程去做他們應(yīng)該做的事了。

    連接池過大的最主要的跡象就是CPU的使用率一直很高。有些時候,垃圾收集也可能導(dǎo)致CPU使用率很高,但是垃圾收集導(dǎo)致的CPU使用率很高和池過大導(dǎo)致的使用率有一個主要的區(qū)別就是:垃圾收集引起的只是短時間的高使用率就象個釘子,而池過大導(dǎo)致的就是一直持續(xù)很高呈線性。

    這個情況發(fā)生的時候,請求會被放在隊列中不被處理,但是不會始終如此,因為請求占用CPU的情況和程序占用的情況造成的后果不同。降低線程池的大小可能會讓請求等待,但是讓請求等待總比為了處理請求而讓CPU忙不過來的好。讓CPU保持持續(xù)的高使用率,同時性能不降低,新請求到來的時候放入到隊列中,這是最理想的程序。考慮下面這個很類似的情況:很多高速公里有交通燈來保證車輛進(jìn)入到擁擠的公里中。在我看來,這些交通燈根本沒用,道理很充分。比如你來了,在交通燈后面的安全線上等待進(jìn)入到高速公路上。如果所有的車輛都同時涌向公里,我們就動彈不得,但是只要減緩涌向高速公路車輛的速度,交通遲早會暢通。事實上,很多的大城市都有這樣功能,但根本沒用,他們真正需要的是一些更多的小路(CPU),涌向高速公路的速度真的降低了,那么交通會變的正常起來。

    設(shè)置一個飽和的池,然后逐步減少連接池大小,一直到CPU占用率為75%到85%之間,同時用戶負(fù)載正常。如果等待隊列大小實在無法控制,考慮下面2中建議:
    1.把你的程序放入代碼模擬器運行,調(diào)整程序代碼
    2.增加額外的硬件

    如果你的用戶負(fù)載超過了環(huán)境能承受的范圍,你應(yīng)該考慮修正代碼減少和CPU的沖突或者增加CPU。

    JDBC連接池

    很多JAVA EE 程序連接到一個后臺數(shù)據(jù)源,大多數(shù)是通過JDBC(JAVA DATABASE CONNECTIVITY)將程序和后臺連接起來。由于創(chuàng)建數(shù)據(jù)庫連接的代價很高,程序服務(wù)器讓在同一個程序服務(wù)器實例下的所有程序共享特定數(shù)量的一些連接。如果一個請求需要連接到數(shù)據(jù)庫,但是數(shù)據(jù)庫的連接池?zé)o法為這個請求創(chuàng)建一個新連接,這個時候請求就會停下來等待連接池完成自己的操作再給她分配一個連接。反過來,如果數(shù)據(jù)庫連接池太大程序服務(wù)器就會浪費資源,并且程序有可能強迫數(shù)據(jù)庫承受過量的負(fù)荷。我們調(diào)試的目的就是盡量減少請求的等待時間和飽和的資源之間之間的沖突,讓一個請求在數(shù)據(jù)庫外等待要比強迫數(shù)據(jù)庫好的多。

    一個程序服務(wù)器如果設(shè)置連接的數(shù)量不合理就會有下面這些特征:
    1.程序運行速度緩慢
    2.CPU使用率低
    3.?dāng)?shù)據(jù)庫連接池使用率非常高
    4.線程等待數(shù)據(jù)庫的連接
    5.線程使用率很高
    6.請求隊列中有待處理的請求(潛在的)
    7.?dāng)?shù)據(jù)庫CPU使用率很低(因為沒有足夠的請求能夠讓他繁忙起來)

    JDBC prepared statements
    和JDBC相關(guān)的另一個重要的設(shè)置就是:為JDBC使用的statement 所預(yù)設(shè)的緩存的大小。當(dāng)你的程序在數(shù)據(jù)庫中運行SQL statement 的時候三下面3個步驟進(jìn)行:
    1.準(zhǔn)備
    2.執(zhí)行
    3.返回數(shù)值

    在準(zhǔn)備階段,數(shù)據(jù)庫驅(qū)動器讓數(shù)據(jù)庫完成隊列中的執(zhí)行計劃。執(zhí)行的時候,數(shù)據(jù)庫執(zhí)行語句并返回指向結(jié)果的引用。在返回的時候,程序重新描述這些結(jié)果并描述出這些被請求的信息。

    數(shù)據(jù)庫驅(qū)動會這樣優(yōu)化程序:首先,你需要去準(zhǔn)備一個statement ,這個statement 它會讓數(shù)據(jù)庫做好執(zhí)行和緩存結(jié)果的準(zhǔn)備。在此同時,數(shù)據(jù)庫驅(qū)動會從緩存中裝載已經(jīng)準(zhǔn)備好的statement ,而不用直接連接到數(shù)據(jù)庫。

    如果prepared statement 設(shè)置太小,數(shù)據(jù)庫驅(qū)動器會被迫去查詢沒有裝載進(jìn)緩存區(qū)的statement ,這就會增加額外的連接到數(shù)據(jù)庫的時間。prepared statement 緩存區(qū)設(shè)置不恰當(dāng)最主要的癥狀就是花費大量的時間去連接相同的statement。這段被浪費的時間本來是為了讓它去裝載后面的調(diào)用的。

    事情變的稍微復(fù)雜了點,緩存prepared statement 是每個statement的基礎(chǔ),就是說在一個statement連接之前都應(yīng)當(dāng)緩存起來。這個增加的復(fù)雜性就產(chǎn)生了一個沖突:如果你有100個prepared statement需要去緩存,但你的連接池中有50個數(shù)據(jù)庫連接,這個時候你就需要有存放5000條預(yù)備語句的內(nèi)存。

    通過跟蹤性能,確定出你程序所執(zhí)行的不重復(fù)的statement 的數(shù)量,并從這些statement 中找出哪些條是頻繁執(zhí)行的。

    Entity bean(實體BEAN)和stateful session bean的緩沖

    無狀態(tài)(stateless)對象可以被放入到池中共享,但象Entity beans和 stateful session bean這樣的有狀態(tài)的對象就需要被緩存,因為這些bean的每個實例都是不相同的。當(dāng)你需要一個有狀態(tài)對象時,你需要明確創(chuàng)建這個對象的特定實例,普通的實例是不能滿足的。類似的,你考慮一個超市類似的情況,你需要個售貨員但他叫什么并不重要,任何售貨員都可以滿足你。也就是,售貨員被放入池中共享,因為你只需要是售貨員就可以,而不是一個叫做史締夫的這個售貨員。當(dāng)你離開超市的時候,你需要帶上你的孩子,不是其他人的孩子,而是你自己的。這個時候,孩子就需要被緩存。

    image
    Figure 10. The application requests an object from the cache that is in the cache, so a reference to that object is returned without making a network trip to the database

    當(dāng)你的緩存區(qū)太小的時候,緩存的性能就會明顯的受到影響。特別是,當(dāng)一個請求去一個已經(jīng)滿了的緩存區(qū)域去請求一個對象的時候,下面的步驟就會執(zhí)行,這些步驟會在圖11中顯示:
    1.????????程序請求一個對象
    2.????????緩存檢測這個對象是否已經(jīng)存在于緩存中
    3.????????緩存決定把一個對象開除出緩存(一般采用的算法是遺棄最近使用次數(shù)最少的對象)
    4.????????把這個對象扔出緩存(稱為passivated)
    5.????????把從數(shù)據(jù)庫中裝載這個新對象并放入到緩存(稱為activated)
    6.????????把指向這個對象的引用返回給程序

    image
    Figure 11. Because the requested object is not in the cache, an object must be selected for removal from the cache and removed from it.

    如果多數(shù)的請求都需要執(zhí)行這些步驟的話,那你采用緩存技術(shù)就不是好的選擇了!如果這些處理步驟頻繁發(fā)生的話,你就需要重新推敲下你的緩存了。回憶一下:從緩存中去除一個對象稱為passivation,從持久存儲區(qū)取出一個對象放入緩存稱為activation。能在緩存中找到的請求(緩存中有此請求的對象)的百分率稱為hit ratio,相反找不到的請求的百分率稱為miss ratio。

    緩存剛被初始化的時候,hit ratio是0,它的activation數(shù)量非常高,因此在初始化后你需要去觀察緩存的性能。初始化以后,你應(yīng)該跟蹤passivation的數(shù)量并把它和與向緩存請求對象的請求的總量相比較,因為passivations只會發(fā)生在緩存被初始化以后。但一般來說,我們更需要關(guān)心緩存的miss ratio。如果miss ratio超過25%,那么緩存可能是太小了。因此,如果missratio的數(shù)量超過75%,那么不是你的緩存設(shè)置的太小就是你不需要緩存這個技術(shù)。

    一旦你覺得你的緩存太小,就去嘗試著增大大小,并測試增加的性能。如果miss ration下降到20%以下,那你的緩存的大小就非常棒了,如果沒有什么效果,那么你就需要和這個程序的技術(shù)員聯(lián)系,看是這個對象是不是需要緩存或者是否應(yīng)該修正程序中這個對象的代碼。

    Staless session bean和message-driven bean池

    Stateless session bean 和message-driven bean 在商業(yè)應(yīng)用方面很重要,不要期望它們會保持自己特有的狀態(tài)信息。當(dāng)你的程序需要使用這些BEAN的商業(yè)功能的時候,它就從一個池中取出一個BEAN實例,用這個實例來調(diào)用一個個方法,用完后再將BEAN的實例再放回到池中。如果你的程序過了一會又需要這個一摸一樣的BEAN,就從池中再得到一個實例,但不能保證你得到的就是上一個實例。池能夠讓程序共享資源,但是會讓你的程序付出潛在的等待時間。如果你無法從池中得到想要的BEAN,請求就會等待,一直到這個BEAN被放入到池中。很多程序服務(wù)器都會把這些池調(diào)整的很好,但是我碰到過因為在環(huán)境中把他們設(shè)置的太小而引發(fā)的不少麻煩。Stateless bean池的大小應(yīng)該和可執(zhí)行線程池的大小一般大,因為一個線程同時只能使用一個對象,再多了就造成浪費的。因此,一些程序服務(wù)器把池的大小和線程的數(shù)量設(shè)置成同樣的數(shù)量。為了保險起見,你應(yīng)該親自把它設(shè)置成這個數(shù)。

    事務(wù)

    使用Enterprise Java的一個好處就是它天生就支持事務(wù)。通過JAVAEE 5 EJB(Enterprise javaBeans)的注釋,你可以控制事務(wù)中方法的使用。事務(wù)會以下面2中方式結(jié)束:
    1.????????事務(wù)提交
    2.????????事務(wù)回滾

    當(dāng)一個事務(wù)被提交的時候,說明它已經(jīng)完全成功了,但是當(dāng)它回滾的時候,就說明發(fā)生了一些錯誤。回滾會是下面2種情況:
    1.????????程序造成的回滾(程序回滾)
    2.????????非程序造成的回滾(非程序回滾)

    通常,程序回滾是因為商業(yè)的規(guī)定。比如一個WEB程序做一個素描畫的價格的調(diào)查,程序可能讓用戶輸入年齡,并且商業(yè)規(guī)定18歲以上才可以進(jìn)入。如果一個16歲的提交了信息,那么程序就會拋出一個錯誤,打開一個網(wǎng)頁告訴他,他年齡還不能參與到這個信息的調(diào)查。因為程序拋出了異常,因此包含在程序中的事務(wù)的就會發(fā)生回滾。這只是普通的程序回滾,只有當(dāng)發(fā)生大量的程序回滾才值得我們注意。

    另一方面,非程序回滾是非常糟糕的。有三種情形的非程序回滾:
    1.????????系統(tǒng)回滾
    2.????????超時回滾
    3.????????資源回滾

    系統(tǒng)回滾意味著程序服務(wù)器中的一些東西非常的糟糕,恢復(fù)的幾率很渺茫。超時回滾就是當(dāng)程序服務(wù)器中的程序處理請求時超時;除非你把超時設(shè)置的很短才會出現(xiàn)這種錯誤。資源回滾就是當(dāng)一個程序服務(wù)器管理內(nèi)部的資源的時候發(fā)生錯誤。例如,如果你設(shè)置你的程序服務(wù)器通過一個簡單的SQL語句去測試數(shù)據(jù)庫的連接,但數(shù)據(jù)庫對于程序服務(wù)器來說是無法連接的,這個時候任何和這個資源相關(guān)的事情都會發(fā)生資源回滾。

    如果發(fā)生非程序回滾,我們應(yīng)該立刻注意,這個是不小的問題,但是你也需要留意程序回滾發(fā)生的頻率。很多時候人們對發(fā)生的異常很敏感,因此你需要哪些異常對你程序來說才是重要的。

    總結(jié)

    盡管各個程序和他們的環(huán)境都各不相同,但是有一些共同的問題困擾著他們。這篇文章的注意力并不是放在程序代碼的問題上,因為把注意力放在因為環(huán)境的問題而導(dǎo)致的低性能的問題上:
    1.內(nèi)存溢出
    2.線程池大小
    3.JDBC連接池大小
    4.JDBC預(yù)先聲明語句緩存大小
    5.緩存大小
    6.池大小
    7.執(zhí)行事務(wù)時候的回滾

    為了有效的診斷性能的問題,你應(yīng)該了解什么問題會導(dǎo)致什么樣的癥狀。如果主要是程序的代碼導(dǎo)致的惡果那你應(yīng)該帶著問題去尋求負(fù)責(zé)代碼的人尋求幫助,但是如果問題是由環(huán)境引起的,那么就要依靠你的操作來解決了。
    問題的根源依賴于很多要素,但是一些指示器可以增加一些你處理問題時候的一些信心,依靠他們可以完全排除一些其他的原因。我希望這個文章能對你排解JAVAEE環(huán)境問題起到幫助。

    作者簡介
    Steven Haines是3本JAVA書籍的作者:The Java Reference Guide (InformIT/Pearson, 2005), Java 2 Primer Plus (SAMS, 2002), and Java 2 From Scratch (QUE, 1999)。另外多次為別人的文章提供了幫助,同時和其他人合著了一些其他的書,同時也是不少軟件雜志的編輯,也是InformIT.com的主持人。作為一個教育家,他在Learning Tree University 合 the University of California 學(xué)校教授了不少JAVA方面的知識。白天他在Quest Software做JAVA EE 5性能架構(gòu)師,同時定義性能跟蹤調(diào)節(jié)軟件以及控制管理大規(guī)模的JAVA EE 5部署的性能,包括那些世界500強企業(yè)。
    posted on 2006-08-04 15:24 黑咖啡 閱讀(441) 評論(0)  編輯  收藏 所屬分類: tec

    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    留言簿(2)

    隨筆分類(67)

    文章分類(43)

    Good Article

    Good Blogs

    Open Source

    最新隨筆

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 在线观看永久免费视频网站| 一本久久免费视频| 亚洲免费视频一区二区三区| 亚洲日韩中文在线精品第一| 亚洲已满18点击进入在线观看| 国内自产拍自a免费毛片| 久久精品免费大片国产大片| 亚洲美女视频网站| 亚洲av麻豆aⅴ无码电影| 91精品国产免费| 特级aaaaaaaaa毛片免费视频| 亚洲国产精彩中文乱码AV| 国内自产少妇自拍区免费| 久久国产精品免费观看| 国产精品久久久久久亚洲影视| 亚洲AV天天做在线观看| 嫩草影院在线播放www免费观看| 久久亚洲精品专区蓝色区| 国产亚洲欧洲Aⅴ综合一区| 天天看免费高清影视| 女人体1963午夜免费视频| 欧美色欧美亚洲另类二区| 亚洲网站在线免费观看| 亚洲Av无码乱码在线观看性色| 国产精品永久免费10000| 亚洲日本在线电影| 免费无码又爽又刺激高潮| 久草免费福利资源站| 立即播放免费毛片一级| 亚洲av无码久久忘忧草| 亚洲av午夜福利精品一区| 国产乱弄免费视频| 一个人免费观看在线视频www| 黄网站免费在线观看| 一级做a爰性色毛片免费| 亚洲AV日韩AV一区二区三曲| 亚洲欧洲日韩综合| 亚洲国产精品一区二区第一页| 亚洲国产成人精品无码久久久久久综合| 国产成人免费网站| 国产成人精品免费视频网页大全 |