<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 的一個(gè)章節(jié),作者Steven Haines分享了他在調(diào)優(yōu)企業(yè)級(jí)JAVA應(yīng)用時(shí)所遇到的常見問題。

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

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

    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請務(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)存溢出錯(cuò)誤

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

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

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

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

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

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

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

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

    圖2和圖3展示的是了小幅度收集如何運(yùn)行

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    正像圖7顯示的,當(dāng)持久空間變滿了的時(shí)候,就引發(fā)垃圾收集,清理了樂園和幸存者空間,但是并不釋放持久空間中的一點(diǎn)內(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è)置持久空間大小時(shí)候,一般考慮128M,除非你的程序有很多的類文件,這個(gè)時(shí)候,你就可以考慮使用256M大小。如果你想讓他能夠裝載所有的類的時(shí)候,就會(huì)導(dǎo)致一個(gè)典型的結(jié)構(gòu)錯(cuò)誤。設(shè)置成512M就足夠了,它僅僅是暫時(shí)的時(shí)間的花費(fèi)。把持久空間設(shè)置成512M大小就象給一個(gè)腳痛的人吃止痛藥,雖然暫時(shí)緩解了痛,但是腳還是沒有好,依然需要醫(yī)生把痛治療好,否則只是把問題延遲了而已。

    線程池

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

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

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

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

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

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

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

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

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

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

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

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

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

    JDBC連接池

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

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

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

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

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

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

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

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

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

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

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

    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ā)生的話,你就需要重新推敲下你的緩存了。回憶一下:從緩存中去除一個(gè)對象稱為passivation,從持久存儲(chǔ)區(qū)取出一個(gè)對象放入緩存稱為activation。能在緩存中找到的請求(緩存中有此請求的對象)的百分率稱為hit ratio,相反找不到的請求的百分率稱為miss ratio。

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

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

    Staless session bean和message-driven bean池

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

    事務(wù)

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

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

    通常,程序回滾是因?yàn)樯虡I(yè)的規(guī)定。比如一個(gè)WEB程序做一個(gè)素描畫的價(jià)格的調(diào)查,程序可能讓用戶輸入年齡,并且商業(yè)規(guī)定18歲以上才可以進(jìn)入。如果一個(gè)16歲的提交了信息,那么程序就會(huì)拋出一個(gè)錯(cuò)誤,打開一個(gè)網(wǎng)頁告訴他,他年齡還不能參與到這個(gè)信息的調(diào)查。因?yàn)槌绦驋伋隽水惓#虼税诔绦蛑械氖聞?wù)的就會(huì)發(fā)生回滾。這只是普通的程序回滾,只有當(dāng)發(fā)生大量的程序回滾才值得我們注意。

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

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

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

    總結(jié)

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

    為了有效的診斷性能的問題,你應(yīng)該了解什么問題會(huì)導(dǎo)致什么樣的癥狀。如果主要是程序的代碼導(dǎo)致的惡果那你應(yīng)該帶著問題去尋求負(fù)責(zé)代碼的人尋求幫助,但是如果問題是由環(huán)境引起的,那么就要依靠你的操作來解決了。
    問題的根源依賴于很多要素,但是一些指示器可以增加一些你處理問題時(shí)候的一些信心,依靠他們可以完全排除一些其他的原因。我希望這個(gè)文章能對你排解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)。另外多次為別人的文章提供了幫助,同時(shí)和其他人合著了一些其他的書,同時(shí)也是不少軟件雜志的編輯,也是InformIT.com的主持人。作為一個(gè)教育家,他在Learning Tree University 合 the University of California 學(xué)校教授了不少JAVA方面的知識(shí)。白天他在Quest Software做JAVA EE 5性能架構(gòu)師,同時(shí)定義性能跟蹤調(diào)節(jié)軟件以及控制管理大規(guī)模的JAVA EE 5部署的性能,包括那些世界500強(qiáng)企業(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

    最新隨筆

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲黄色免费观看| 久久久久亚洲精品成人网小说| 亚洲日韩国产精品乱| 亚洲精品卡2卡3卡4卡5卡区| 亚洲午夜久久久精品影院| 亚洲一区二区三区在线观看蜜桃| 亚洲乱妇老熟女爽到高潮的片| 人妻仑乱A级毛片免费看| 国产无遮挡裸体免费视频在线观看| 日韩在线播放全免费| 免费国产精品视频| 亚洲国产精品无码久久SM| 亚洲国产成人手机在线电影bd| 激情无码亚洲一区二区三区 | www.亚洲色图.com| 亚洲国产精品成人精品无码区| 亚洲人成影院77777| 特级aa**毛片免费观看| 久久一区二区三区免费播放| 毛片a级三毛片免费播放| 亚洲综合色视频在线观看| 亚洲性色高清完整版在线观看| 偷自拍亚洲视频在线观看99| 在线观看人成视频免费无遮挡| 日韩版码免费福利视频| 中文字幕亚洲日韩无线码| 亚洲人成在线精品| 成人一区二区免费视频| 成年在线观看网站免费| 亚洲五月综合缴情在线观看| 亚洲偷自精品三十六区| 三级毛片在线免费观看| 天天拍拍天天爽免费视频| 国产亚洲福利精品一区| 亚洲AV成人一区二区三区观看| 无码人妻精品中文字幕免费 | 亚洲精品中文字幕无码AV| 免费无码国产V片在线观看| 麻花传媒剧在线mv免费观看| 亚洲一级片内射网站在线观看| 亚洲综合色7777情网站777|