Glassfish性能調(diào)優(yōu) 讓你的Java EE更流暢
GlassFish是目前主流的Java EE應(yīng)用服務(wù)器之一,目前相當(dāng)數(shù)量的Java企業(yè)級(jí)應(yīng)用運(yùn)行在GlassFish上,性能調(diào)優(yōu)就成為每個(gè)GlassFish上的Java開發(fā)者關(guān)注的問(wèn)題.
其實(shí),Java EE應(yīng)用的性能問(wèn)題對(duì)嚴(yán)肅的項(xiàng)目和產(chǎn)品來(lái)說(shuō)是一個(gè)非常重要的問(wèn)題。特別是企業(yè)級(jí)的應(yīng)用,并發(fā)用戶多,數(shù)據(jù)傳輸量大,業(yè)務(wù)邏輯復(fù)雜,占用系統(tǒng)資源多,因此性能問(wèn)題在企業(yè)級(jí)應(yīng)用變得至關(guān)重要,它和系統(tǒng)的穩(wěn)定性有著直接的聯(lián)系。更加重要的是,性能好的應(yīng)用在完成相同任務(wù)的條件下,能夠占用更少的資源,獲得更好的用戶體驗(yàn),換句話說(shuō),就是能夠節(jié)省費(fèi)用和消耗,獲得更高的利潤(rùn)。
要獲得更好的性能,就需要對(duì)原來(lái)的系統(tǒng)進(jìn)行性能調(diào)優(yōu)。對(duì)運(yùn)行在Glassfish上的JavaEE應(yīng)用,Glassfish性能調(diào)優(yōu)是一件相對(duì)復(fù)雜的事情。在調(diào)優(yōu)以前必須要認(rèn)識(shí)到:對(duì)JavaEE的系統(tǒng),調(diào)優(yōu)是多層次的。一個(gè)JavaEE的應(yīng)用其實(shí)是整個(gè)系統(tǒng)中很少的一部分。開發(fā)人員所開發(fā)的JavaEE程序,無(wú)論是JSP還是 EJB,都是運(yùn)行在JavaEE應(yīng)用服務(wù)器(Glassfish)之上。而應(yīng)用服務(wù)器本身也是Java語(yǔ)言編寫的,需要運(yùn)行在Java虛擬機(jī)之上。 Java虛擬機(jī)也只不過(guò)是操作系統(tǒng)的一個(gè)應(yīng)用而已,和其他的應(yīng)用(如Apache)對(duì)于操作系統(tǒng)來(lái)說(shuō)沒(méi)有本質(zhì)的區(qū)別。而操作系統(tǒng)卻運(yùn)行在一定的硬件環(huán)境中,包括CPU,內(nèi)存,網(wǎng)卡和硬盤等等。在這么多的層次中,每一個(gè)層次的因素都會(huì)影響整個(gè)系統(tǒng)的性能。因此,對(duì)一個(gè)系統(tǒng)的調(diào)優(yōu),事實(shí)上需要同時(shí)對(duì)每個(gè)層次都要調(diào)優(yōu)。JavaEE應(yīng)用性能調(diào)優(yōu)不僅僅和Glassfish有關(guān),Java語(yǔ)言有關(guān),還要和操作系統(tǒng)以及硬件都有關(guān)系,需要調(diào)優(yōu)者有綜合的知識(shí)和技能。這些不同層面的方法需要綜合縱效,結(jié)合在一起靈活使用,才能快速有效的定位性能瓶頸。下面是一些具體的案例分析:
Glassfish性能調(diào)優(yōu)之內(nèi)存泄漏問(wèn)題
某個(gè)JavaEE應(yīng)用運(yùn)行在8顆CPU的服務(wù)器上。上線運(yùn)行發(fā)現(xiàn)性能不穩(wěn)定。性能隨著時(shí)間的增加而越來(lái)越慢。通過(guò)操作系統(tǒng)的工具(mpstat),發(fā)現(xiàn)在系統(tǒng)很慢的時(shí)候,只有一顆CPU很忙,其他的CPU都很空閑。因此懷疑是Java虛擬機(jī)經(jīng)常進(jìn)行內(nèi)存回收,因?yàn)樘摂M機(jī)在內(nèi)存回收的時(shí)候,有的回收算法通常只能運(yùn)行在一個(gè)CPU上。通過(guò)Java虛擬機(jī)的工具“jstat”可以清楚的看到,Java虛擬機(jī)進(jìn)行內(nèi)存回收的頻率非常高,幾乎每5秒中就有一次,每次回收的時(shí)間為2秒鐘。另外,通過(guò)“jstat”的輸出還發(fā)現(xiàn)每次回收釋放的內(nèi)存非常有限,大多數(shù)對(duì)象都無(wú)法回收。這種現(xiàn)象很大程度上暗示著內(nèi)存泄漏。使用 Java虛擬機(jī)的工具“jmap”來(lái)獲得當(dāng)前的一個(gè)內(nèi)存映象。發(fā)現(xiàn)有很多(超過(guò)10000)個(gè)的session對(duì)象。
這是不正常的一個(gè)現(xiàn)象。一般來(lái)說(shuō), session對(duì)應(yīng)于一個(gè)用戶的多次訪問(wèn),當(dāng)用戶退出的時(shí)候,session就應(yīng)該失效,對(duì)象應(yīng)該被回收。當(dāng)我們和這個(gè)系統(tǒng)的開發(fā)工程師了解有關(guān) session的設(shè)置,發(fā)現(xiàn)當(dāng)他們部署應(yīng)用的時(shí)候,竟然將session的timeout時(shí)間設(shè)置為50分鐘,并且沒(méi)有提供logout的接口。這樣的設(shè)置下,每個(gè)session的數(shù)據(jù)都會(huì)保存50分鐘才會(huì)被回收。根據(jù)我們的建議,系統(tǒng)提供了logout的鏈接,并且告訴用戶如果退出應(yīng)用,應(yīng)該點(diǎn)擊這個(gè) logout的鏈接;并且將session的timeout時(shí)間修改為5分鐘。通過(guò)幾天的測(cè)試,證明泄漏的問(wèn)題得到解決。
Glassfish性能調(diào)優(yōu)之?dāng)?shù)據(jù)庫(kù)連接池問(wèn)題
某財(cái)務(wù)應(yīng)用運(yùn)行在JavaEE服務(wù)器上,后臺(tái)連接Oracle數(shù)據(jù)庫(kù)。并發(fā)用戶數(shù)量超過(guò)100人左右的時(shí)候系統(tǒng)停止響應(yīng)。通過(guò)操作系統(tǒng)層面的進(jìn)程監(jiān)控工具發(fā)現(xiàn)進(jìn)程并沒(méi)有被殺死或掛起,而CPU使用率幾乎為零。那么是什么原因?qū)е孪到y(tǒng)停止響應(yīng)用戶請(qǐng)求呢?我們利用Java虛擬機(jī)的工具(kill -3 pid)將當(dāng)前的所有線程狀態(tài)DUMP出來(lái),發(fā)現(xiàn)JavaEE服務(wù)器的大部分處理線程都在等待數(shù)據(jù)庫(kù)連接池的連接,而那些已經(jīng)獲得數(shù)據(jù)庫(kù)連接的線程卻處于阻塞狀態(tài)。數(shù)據(jù)庫(kù)管理員應(yīng)要求檢查了數(shù)據(jù)庫(kù)的狀態(tài),發(fā)現(xiàn)所有的連接的session都處于死鎖狀態(tài)。顯然,這是因?yàn)閿?shù)據(jù)庫(kù)端出現(xiàn)了死鎖的操作,阻塞了那些有數(shù)據(jù)庫(kù)操作的請(qǐng)求,占用了所有數(shù)據(jù)庫(kù)連接池中的連接。后續(xù)的請(qǐng)求如果還要從連接池中獲取連接,就會(huì)阻塞在連接池上。當(dāng)解決數(shù)據(jù)庫(kù)死鎖的問(wèn)題之后,性能問(wèn)題迎刃而解。
Glassfish性能調(diào)優(yōu)之大對(duì)象緩存問(wèn)題
電信應(yīng)用運(yùn)行在64位Java虛擬機(jī)上,系統(tǒng)運(yùn)行得很不穩(wěn)定,系統(tǒng)經(jīng)常停止響應(yīng)。使用進(jìn)程工具查看,發(fā)現(xiàn)進(jìn)程并沒(méi)有被殺死或掛起。利用Java虛擬機(jī)的工具發(fā)現(xiàn)系統(tǒng)在長(zhǎng)時(shí)間的進(jìn)行內(nèi)存回收,內(nèi)存回收的時(shí)間長(zhǎng)達(dá)15分鐘,整個(gè)系統(tǒng)在內(nèi)存回收的時(shí)候就像掛起一樣。另外還觀察到系統(tǒng)使用了12G的內(nèi)存(因?yàn)槭?64位虛擬機(jī)所以突破了4G內(nèi)存的限制)。從開發(fā)人員那里了解到,這個(gè)應(yīng)用為了提高性能,大量使用了對(duì)象緩存,但是事與愿違,在Java中使用過(guò)多的內(nèi)存,雖然在正常運(yùn)行的時(shí)候能夠獲得很好的性能,但是會(huì)大大增加內(nèi)存回收的時(shí)間。特別是對(duì)象緩存,本系統(tǒng)使用了8G的緩存空間,共緩存了6000多萬(wàn)個(gè)對(duì)象,對(duì)這些對(duì)象的遍歷導(dǎo)致了長(zhǎng)時(shí)間的內(nèi)存回收。根據(jù)我們的建議,將緩存空間減少到1G,并調(diào)整回收算法(使用增量回收的算法),使得系統(tǒng)由于內(nèi)存回收而造成的最大停頓時(shí)間減少到4秒,基本滿足用戶的需求。
Glassfish性能調(diào)優(yōu)之外部命令問(wèn)題
數(shù)字校園應(yīng)用運(yùn)行在4CPU的Solaris10服務(wù)器上,中間件為JavaEE服務(wù)器。系統(tǒng)在做大并發(fā)壓力測(cè)試的時(shí)候,請(qǐng)求響應(yīng)時(shí)間比較慢,通過(guò)操作系統(tǒng)的工具(mpstat)發(fā)現(xiàn)CPU使用率比較高。并且系統(tǒng)占用絕大多數(shù)的CPU資源而不是應(yīng)用本身。這是個(gè)不正常的現(xiàn)象,通常情況下用戶應(yīng)用的CPU占用率應(yīng)該占主要地位,才能說(shuō)明系統(tǒng)是正常工作。通過(guò)Solaris 10的Dtrace腳本,我們查看當(dāng)前情況下哪些系統(tǒng)調(diào)用花費(fèi)了最多的CPU資源,竟然發(fā)現(xiàn)最花費(fèi)CPU的系統(tǒng)調(diào)用是“fork”。眾所周知, “fork”系統(tǒng)調(diào)用是用來(lái)產(chǎn)生新的進(jìn)程,在Java虛擬機(jī)中只有線程的概念,絕不會(huì)有進(jìn)程的產(chǎn)生。這是個(gè)非常異常的現(xiàn)象。通過(guò)本系統(tǒng)的開發(fā)人員,我們找到了答案:每個(gè)用戶請(qǐng)求的處理都包含執(zhí)行一個(gè)外部shell腳本,來(lái)獲得系統(tǒng)的一些信息。這是通過(guò)Java的“Runtime.getRuntime ().exec”來(lái)完成的,但是這種方法在Java中非常消耗資源。Java虛擬機(jī)執(zhí)行這個(gè)命令的方式是:首先克隆一個(gè)和當(dāng)前虛擬機(jī)一樣的進(jìn)程,再用這個(gè)新的進(jìn)程去執(zhí)行外部命令,最后再退出這個(gè)進(jìn)程。如果頻繁執(zhí)行這個(gè)操作,系統(tǒng)的消耗會(huì)很大,不僅在CPU,內(nèi)存操作也很重。用戶根據(jù)建議去掉這個(gè)shell 腳本執(zhí)行的語(yǔ)句,系統(tǒng)立刻回復(fù)了正常。
Glassfish性能調(diào)優(yōu)之文件操作問(wèn)題
內(nèi)容管理(CMS)系統(tǒng)運(yùn)行在JavaEE服務(wù)器上,當(dāng)系統(tǒng)長(zhǎng)時(shí)間運(yùn)行以后,性能非常差,用戶請(qǐng)求的延時(shí)比系統(tǒng)剛上線的時(shí)候要大很多,并且用戶的并發(fā)量很小,甚至是單個(gè)用戶也很慢。通過(guò)操作系統(tǒng)的工具觀察,一切都很正常,CPU利用率不高,IO也不是很大,內(nèi)存很富余,網(wǎng)絡(luò)幾乎沒(méi)有壓力(因?yàn)椴l(fā)用戶少)。先不考慮線程互鎖的問(wèn)題,因?yàn)閱蝹€(gè)用戶性能也不好。通過(guò)Java虛擬機(jī)觀察也沒(méi)有發(fā)現(xiàn)什么問(wèn)題(內(nèi)存回收很少發(fā)生)。這使得我們不得不使用代碼跟蹤器來(lái)全程跟蹤代碼。我們采用了Netbeans的Profiler,跟蹤的結(jié)果非常意外,用戶請(qǐng)求的90%的時(shí)間在創(chuàng)建新文件。從系統(tǒng)設(shè)計(jì)人員了解到,此系統(tǒng)使用了一個(gè)目錄用于保存所有上傳和共享的文件,文件用其命名方式來(lái)唯一區(qū)別于其他文件。我們查看了那個(gè)文件目錄,發(fā)現(xiàn)該目錄下已經(jīng)擁有80萬(wàn)個(gè)文件了。這時(shí)候我們才定位到問(wèn)題了:在同個(gè)目錄下放置太多的文件,在創(chuàng)建新文件的時(shí)候,系統(tǒng)的開銷是比較大的,例如為了防止重名,文件系統(tǒng)會(huì)遍歷當(dāng)前目錄下所有的文件名等等。根據(jù)我們的建議,將文件分類保存在不同的目錄下,性能有了大幅度的提高。
Glassfish性能調(diào)優(yōu)之高速緩存命中率問(wèn)題
運(yùn)行在JavaEE服務(wù)器上的ERP系統(tǒng),在CPU充分利用的情況下性能仍然不太好。從操作系統(tǒng)層面上觀察不到什么大問(wèn)題,而且ERP系統(tǒng)過(guò)于復(fù)雜,代碼跟蹤比較困難。于是進(jìn)行了CPU狀態(tài)的進(jìn)一步檢查,發(fā)現(xiàn)CPU的TLB命中率不是很高,于是對(duì)Java虛擬機(jī)的啟動(dòng)參數(shù)進(jìn)行了修改,強(qiáng)迫虛擬機(jī)使用大尺寸的內(nèi)存頁(yè)面,提高TLB的命中率。下面的參數(shù)是在Sun的HOTSPOT中調(diào)整大尺寸(4M)頁(yè)面的設(shè)置:
- -XX:+AggressiveHeap
- -XX:LargePageSizeInBytes=256m
通過(guò)調(diào)整,TLB命中明顯提高,性能也得到近40%的提升。
【編輯推薦】