做網(wǎng)站有2個前提條件 一個是網(wǎng)站百度推廣怎么提高關(guān)鍵詞排名
案例1:大內(nèi)存硬件上的程序部署策略
一個15萬PV/日左右的在線文檔類型網(wǎng)站最近更換了硬件系統(tǒng),服務(wù)器的硬件為四路志強(qiáng)處理器、16GB物理內(nèi)存,操作系統(tǒng)為64位CentOS 5.4,Resin作為Web服務(wù)器。整個服務(wù)器暫時沒有部署別的應(yīng)用,所有硬件資源都可以提供給這訪問量并不算太大的文檔網(wǎng)站使用。軟件版本選用的是64位的JDK 5,管理員啟用了一個虛擬機(jī)實例,使用-Xmx和-Xms參數(shù)將Java堆大小固定在12GB。
監(jiān)控服務(wù)器運(yùn)行狀況后發(fā)現(xiàn)網(wǎng)站失去響應(yīng)是由垃圾收集停頓所導(dǎo)致的,在該系統(tǒng)軟硬件條件下,HotSpot虛擬機(jī)是以服務(wù)端模式運(yùn)行,默認(rèn)使用的是吞吐量優(yōu)先收集器,回收12GB的Java堆,一次FullGC的停頓時間就高達(dá)14秒。由于程序設(shè)計的原因,訪問文檔時會把文檔從磁盤提取到內(nèi)存中,導(dǎo)致內(nèi)存中出現(xiàn)很多由文檔序列化產(chǎn)生的大對象,這些大對象大多在分配時就直接進(jìn)入了老年代,沒有在Minor GC中被清理掉。這種情況下即使有12GB的堆,內(nèi)存也很快會被消耗殆盡。
主要問題:過大的堆內(nèi)存進(jìn)行回收時帶來的長時間的停頓
單體應(yīng)用在較大內(nèi)存的硬件上主要的部署方式有兩種:
1)通過一個單獨的Java虛擬機(jī)實例來管理大量的Java堆內(nèi)存。
使用單個Java虛擬機(jī)實例來管理大內(nèi)存,還需要考慮下面可能面臨的問題:
Ⅰ回收大塊堆內(nèi)存而導(dǎo)致的長時間停頓,自從G1收集器的出現(xiàn),增量回收得到比較好的應(yīng)用
Ⅱ大內(nèi)存必須有64位Java虛擬機(jī)的支持,但由于壓縮指針、處理器緩存行容量(Cache Line)等因
素,64位虛擬機(jī)的性能測試結(jié)果普遍略低于相同版本的32位虛擬機(jī)。
Ⅲ必須保證應(yīng)用程序足夠穩(wěn)定,因為這種大型單體應(yīng)用要是發(fā)生了堆內(nèi)存溢出,幾乎無法產(chǎn)生堆轉(zhuǎn)
儲快照(要產(chǎn)生十幾GB乃至更大的快照文件),哪怕成功生成了快照也難以進(jìn)行分析;如果確實出了
問題要進(jìn)行診斷,可能就必須應(yīng)用JMC這種能夠在生產(chǎn)環(huán)境中進(jìn)行的運(yùn)維工具
2)同時使用若干個Java虛擬機(jī),建立邏輯集群來利用硬件資源。
做法是在一臺物理機(jī)器上啟動多個應(yīng)用服務(wù)器進(jìn)程,為每個服務(wù)器進(jìn)程分配不同端口,然后在前端搭建一個負(fù)載均衡器,以反向代理的方式來分配訪問請求。
缺點:
Ⅰ節(jié)點競爭全局的資源,最典型的就是磁盤競爭,各個節(jié)點如果同時訪問某個磁盤文件的話(尤其是并發(fā)寫操作容易出現(xiàn)問題),很容易導(dǎo)致I/O異常。
Ⅱ很難最高效率地利用某些資源池,譬如連接池,一般都是在各個節(jié)點建立自己獨立的連接池,這樣有可能導(dǎo)致一些節(jié)點的連接池已經(jīng)滿了,而另外一些節(jié)點仍有較多空余。盡管可以使用集中式的JNDI來解決,但這個方案有一定復(fù)雜性并且可能帶來額外的性能代價。
解決方案:
調(diào)整為建立5個32位JDK的邏輯集群,每個進(jìn)程按2GB內(nèi)存計算(其中堆固定為1.5GB),占用了10GB內(nèi)存。另外建立一個Apache服務(wù)作為前端均衡代理作為訪問門戶??紤]到用戶對響應(yīng)速度比較關(guān)心,并且文檔服務(wù)的主要壓力集中在磁盤和內(nèi)存訪問,處理器資源敏感度較低,因此改為CMS收集器進(jìn)行垃圾回收。
案例2:集群間同步導(dǎo)致的內(nèi)存溢出
一個基于B/S的MIS系統(tǒng),硬件為兩臺雙路處理器、8GB內(nèi)存的HP小型機(jī),應(yīng)用中間件是WebLogic9.2,每臺機(jī)器啟動了3個WebLogic實例,構(gòu)成一個6個節(jié)點的親合式集群。由于是親合式集群,節(jié)點之間沒有進(jìn)行Session同步,但是有一些需求要實現(xiàn)部分?jǐn)?shù)據(jù)在各個節(jié)點間共享。最開始這些數(shù)據(jù)是存放在數(shù)據(jù)庫中的,但由于讀寫頻繁、競爭很激烈,性能影響較大,后面使用JBossCache構(gòu)建了一個全局緩存。全局緩存啟用后,服務(wù)正常使用了一段較長的時間。但在最近不定期出現(xiàn)多次的內(nèi)存溢出問題。
最近一次溢出之后,堆轉(zhuǎn)儲快照里面存在著大量的org.jgroups.protocols.pbcast.NAKACK對象
JBossCache是基于自家的JGroups進(jìn)行集群間的數(shù)據(jù)通信,JGroups使用協(xié)議棧的方式來實現(xiàn)收發(fā)數(shù)據(jù)包的各種所需特性自由組合,數(shù)據(jù)包接收和發(fā)送時要經(jīng)過每層協(xié)議棧的up()和down()方法,其中的NAKACK棧用于保障各個包的有效順序以及重發(fā)。
由于信息有傳輸失敗需要重發(fā)的可能性,在確認(rèn)所有注冊在GMS(Group Membership Service)的節(jié)點都收到正確的信息前,發(fā)送的信息必須在內(nèi)存中保留。當(dāng)網(wǎng)絡(luò)情況不能滿足傳輸要求時,重發(fā)數(shù)據(jù)在內(nèi)存中不斷堆積,很快就產(chǎn)生了內(nèi)存溢出。
案例3:堆外內(nèi)存導(dǎo)致的溢出錯誤
基于B/S的電子考試系統(tǒng),為了實現(xiàn)客戶端能實時地從服務(wù)器端接收考試數(shù)據(jù),系統(tǒng)使用了逆向AJAX技術(shù)(也稱為Comet或者Server Side Push),選用CometD 1.1.1作為服務(wù)端推送框架,服務(wù)器是Jetty 7.1.4,硬件為一臺很普通PC機(jī),Core i5 CPU,4GB內(nèi)存,運(yùn)行32位Windows操作系統(tǒng)。
問題:服務(wù)端不定時拋出內(nèi)存溢出異常,加入-XX:+HeapDumpOnOutOfMemoryError參數(shù),居然也沒有任何反應(yīng),拋出內(nèi)存溢出異常時什么文件都沒有產(chǎn)生。無奈之下只好掛著jstat緊盯屏幕,發(fā)現(xiàn)垃圾收集并不頻繁,Eden區(qū)、Survivor區(qū)、老年代以及方法區(qū)的內(nèi)存全部都很穩(wěn)定,壓力并不大,但就是照樣不停拋出內(nèi)存溢出異常。在內(nèi)存溢出后從系統(tǒng)日志中找到異常堆棧
問題出在直接內(nèi)存,虛擬機(jī)雖然會對直接內(nèi)存進(jìn)行回收,但是直接內(nèi)存卻不能像新生代、老年代那樣,發(fā)現(xiàn)空間不足了就主動通知收集器進(jìn)行垃圾回收,它只能等待老年代滿后Full GC出現(xiàn)后,“順便”幫它清理掉內(nèi)存的廢棄對
象。否則就不得不一直等到拋出內(nèi)存溢出異常時,先捕獲到異常
從實踐經(jīng)驗的角度出發(fā),在處理小內(nèi)存或者32位的應(yīng)用問題時,除了Java堆和方法區(qū)之外,我們注意到下面這些區(qū)域還會占用較多的內(nèi)存,這里所有的內(nèi)存總和受到操作系統(tǒng)進(jìn)程最大內(nèi)存的限制:
直接內(nèi)存:可通過-XX:MaxDirectMemorySize調(diào)整大小,內(nèi)存不足時拋出OutOf-MemoryError或
者OutOfMemoryError:Direct buffer memory。
線程堆棧:可通過-Xss調(diào)整大小,內(nèi)存不足時拋出StackOverflowError(如果線程請求的棧深度大于虛擬機(jī)所允許的深度)或者OutOfMemoryError(如果Java虛擬機(jī)棧容量可以動態(tài)擴(kuò)展,當(dāng)棧擴(kuò)展時無法申請到足夠的內(nèi)存)。
Socket緩存區(qū):每個Socket連接都Receive和Send兩個緩存區(qū),分別占大約37KB和25KB內(nèi)存,連接多的話這塊內(nèi)存占用也比較可觀。如果無法分配,可能會拋出IOException:Too many open files異常。
JNI代碼:如果代碼中使用了JNI調(diào)用本地庫,那本地庫使用的內(nèi)存也不在堆中,而是占用Java虛擬機(jī)的本地方法棧和本地內(nèi)存的。
案例4:外部命令導(dǎo)致系統(tǒng)緩慢
一個數(shù)字校園應(yīng)用系統(tǒng),運(yùn)行在一臺四路處理器的Solaris 10操作系統(tǒng)上,中間件為GlassFish服務(wù)器。系統(tǒng)在做大并發(fā)壓力測試的時候,發(fā)現(xiàn)請求響應(yīng)時間比較慢,通過操作系統(tǒng)的mpstat工具發(fā)現(xiàn)處理器使用率很高,但是系統(tǒng)中占用絕大多數(shù)處理器資源的程序并不是該應(yīng)用本身。
**原因:發(fā)現(xiàn)最消耗處理器資源的竟然是“fork”**系統(tǒng)調(diào)用。眾所周知,“fork”系統(tǒng)調(diào)用是Linux用來產(chǎn)生新進(jìn)程的,在Java虛擬機(jī)中,用戶編寫的Java代碼通常最多只會創(chuàng)建新的線程,不應(yīng)當(dāng)有進(jìn)程的產(chǎn)生,這又是個相當(dāng)不正常的現(xiàn)象。
每個用戶請求的處理都需要執(zhí)行一個外部Shell腳本來獲得系統(tǒng)的一些信息。**執(zhí)行這個Shell腳本是通過Java的Runtime.getRuntime().exec()**方法來調(diào)用的。這種調(diào)用方式可以達(dá)到執(zhí)行Shell腳本的目的,但是它在Java虛擬機(jī)中是非常消耗資源的操作,即使外部命令本身能很快執(zhí)行完畢,頻繁調(diào)用時創(chuàng)建進(jìn)程的開銷也會非??捎^。Java虛擬機(jī)執(zhí)行這個命令的過程是首先復(fù)制一個和當(dāng)前虛擬機(jī)擁有一樣環(huán)境變量的進(jìn)程,再用這個新的進(jìn)程去執(zhí)行外部命令,最后再退出這個進(jìn)程。如果頻繁執(zhí)行這個操作,系統(tǒng)的消耗必然會很大,而且不僅是處理器消耗,內(nèi)存負(fù)擔(dān)也很重。
**解決辦法:**去掉這個Shell腳本執(zhí)行的語句
案例5:服務(wù)器虛擬機(jī)進(jìn)程崩潰
一個基于B/S的MIS系統(tǒng),硬件為兩臺雙路處理器、8GB內(nèi)存的HP系統(tǒng),服務(wù)器是WebLogic9.2(與第二個案例中那套是同一個系統(tǒng))。正常運(yùn)行一段時間后,最近發(fā)現(xiàn)在運(yùn)行期間頻繁出現(xiàn)集群節(jié)點的虛擬機(jī)進(jìn)程自動關(guān)閉的現(xiàn)象,留下了一個hs_err_pid###.log文件后,虛擬機(jī)進(jìn)程就消失了,兩臺物理機(jī)器里的每個節(jié)點都出現(xiàn)過進(jìn)程崩潰的現(xiàn)象。從系統(tǒng)日志中注意到,每個節(jié)點的虛擬機(jī)進(jìn)程在崩潰之前,都發(fā)生過大量相同的異常
這是一個遠(yuǎn)端斷開連接的異常,通過系統(tǒng)管理員了解到系統(tǒng)最近與一個OA門戶做了集成,在MIS系統(tǒng)工作流的待辦事項變化時,要通過Web服務(wù)通知OA門戶系統(tǒng),把待辦事項的變化同步到OA門戶之中。通過SoapUI測試了一下同步待辦事項的幾個Web服務(wù),發(fā)現(xiàn)調(diào)用后竟然需要長達(dá)3分鐘才能返回,并且返回結(jié)果都是超時導(dǎo)致的連接中斷。
由于MIS系統(tǒng)的用戶多,待辦事項變化很快,為了不被OA系統(tǒng)速度拖累,使用了異步的方式調(diào)用Web服務(wù),但由于兩邊服務(wù)速度的完全不對等,時間越長就累積了越多Web服務(wù)沒有調(diào)用完成,導(dǎo)致在等待的線程和Socket連接越來越多,最終超過虛擬機(jī)的承受能力后導(dǎo)致虛擬機(jī)進(jìn)程崩潰。通知OA門戶方修復(fù)無法使用的集成接口,并將異步調(diào)用改為生產(chǎn)者/消費(fèi)者模式的消息隊列實現(xiàn)后,系統(tǒng)恢復(fù)正常
案例6:不恰當(dāng)數(shù)據(jù)結(jié)構(gòu)導(dǎo)致內(nèi)存占用過大
一個后臺RPC服務(wù)器,使用64位Java虛擬機(jī),內(nèi)存配置為-Xms4g-Xmx8g-Xmn1g,使用ParNew加CMS的收集器組合。平時對外服務(wù)的Minor GC時間約在30毫秒以內(nèi),完全可以接受。但業(yè)務(wù)上需要每10分鐘加載一個約80MB的數(shù)據(jù)文件到內(nèi)存進(jìn)行數(shù)據(jù)分析,這些數(shù)據(jù)會在內(nèi)存中形成超過100萬個HashMap<Long,Long>Entry,在這段時間里面Minor GC就會造成超過500毫秒的停頓
產(chǎn)生問題的根本原因是用HashMap<Long,Long>結(jié)構(gòu)來存儲數(shù)據(jù)文件空間效率太低了
我們具體分析一下HashMap空間效率,在HashMap<Long,Long>結(jié)構(gòu)中,只有Key和Value所存放的兩個長整型數(shù)據(jù)是有效數(shù)據(jù),共16字節(jié)(2×8字節(jié))。這兩個長整型數(shù)據(jù)包裝成java.lang.Long對象之后,就分別具有8字節(jié)的Mark Word、8字節(jié)的Klass指針,再加8字節(jié)存儲數(shù)據(jù)的long值。然后這2個Long對象組成Map.Entry之后,又多了16字節(jié)的對象頭,然后一個8字節(jié)的next字段和4字節(jié)的int型的hash字段,為了對齊,還必須添加4字節(jié)的空白填充,最后還有HashMap中對這個Entry的8字節(jié)的引用,這樣增加兩個長整型數(shù)字,實際耗費(fèi)的內(nèi)存為(Long(24byte)×2)+Entry(32byte)+HashMapRef(8byte)=88byte,空間效率為有效數(shù)據(jù)除以全部內(nèi)存空間,即16字節(jié)/88字節(jié)=18%,這確實太低了
案例7:由Windows虛擬內(nèi)存導(dǎo)致的長時間停頓
有一個帶心跳檢測功能的GUI桌面程序,每15秒會發(fā)送一次心跳檢測信號,如果對方30秒以內(nèi)都沒有信號返回,那就認(rèn)為和對方程序的連接已經(jīng)斷開。
問題:
程序上線后發(fā)現(xiàn)心跳檢測有誤報的可能,查詢?nèi)罩景l(fā)現(xiàn)誤報的原因是程序會偶爾出現(xiàn)間隔約一分鐘的時間完全無日志輸出,處于停頓狀態(tài)
出現(xiàn)原因:當(dāng)它最小化的時候,資源管理中顯示的占用內(nèi)存大幅度減小,但是虛擬內(nèi)存則沒有變化,因此懷疑程序在最小化時它的工作內(nèi)存被自動交換到磁盤的頁面文件之中了,這樣發(fā)生垃圾收集時就有可能因為恢復(fù)頁面文件的操作導(dǎo)致不正常的垃圾收集停頓。
**解決方法:**可以加入?yún)?shù)“-Dsun.awt.keepWorkingSetOnMinimize=true”來解決。這個參數(shù)在許多AWT的程序上都有應(yīng)用,例如JDK(曾經(jīng))自帶的VisualVM,啟動配置文件中就有這個參數(shù),保證程序在恢復(fù)最小化時能夠立即響應(yīng)。在這個案例中加入該參數(shù),問題馬上得到解決。
案例8:由安全點導(dǎo)致長時間停頓
有一個比較大的承擔(dān)公共計算任務(wù)的離線HBase集群,運(yùn)行在JDK 8上,使用G1收集器。每天都有大量的MapReduce或Spark離線分析任務(wù)對其進(jìn)行訪問,同時有很多其他在線集群Replication過來的數(shù)據(jù)寫入,因為集群讀寫壓力較大,而離線分析任務(wù)對延遲又不會特別敏感,所以將-XX:MaxGCPauseMillis參數(shù)設(shè)置到了500毫秒。不過運(yùn)行一段時間后發(fā)現(xiàn)垃圾收集的停頓經(jīng)常達(dá)到3秒以上,而且實際垃圾收集器進(jìn)行回收的動作就只占其中的幾百毫秒
user:進(jìn)程執(zhí)行用戶態(tài)代碼所耗費(fèi)的處理器時間。
·sys:進(jìn)程執(zhí)行核心態(tài)代碼所耗費(fèi)的處理器時間。
·real:執(zhí)行動作從開始到結(jié)束耗費(fèi)的時鐘時間。
請注意,前面兩個是處理器時間,而最后一個是時鐘時間,它們的區(qū)別是處理器時間代表的是線程占用處理器一個核心的耗時計數(shù),而時鐘時間就是現(xiàn)實世界中的時間計數(shù)。如果是單核單線程的場景下,這兩者可以認(rèn)為是等價的,但如果是多核環(huán)境下,同一個時鐘時間內(nèi)有多少處理器核心正在工作,就會有多少倍的處理器時間被消耗和記錄下來
問題:日志中的2255毫秒自旋(Spin)時間就是指由于部分線程已經(jīng)走到了安全點,但還有一些特別慢的線程并沒有到,所以垃圾收集線程無法開始工作,只能空轉(zhuǎn)(自旋)等待
**原因及解決方法:**最終查明導(dǎo)致這個問題是HBase中一個連接超時清理的函數(shù),由于集群會有多個MapReduce或Spark任務(wù)進(jìn)行訪問,而每個任務(wù)又會同時起多個Mapper/Reducer/Executer,其每一個都會作為一個HBase的客戶端,這就導(dǎo)致了同時連接的數(shù)量會非常多。更為關(guān)鍵的是,清理連接的索引值就是int類型,所以這是一個可數(shù)循環(huán),HotSpot不會在循環(huán)中插入安全點。當(dāng)垃圾收集發(fā)生時,如果RpcServer的Listener線程剛好執(zhí)行到該函數(shù)里的可數(shù)循環(huán)時,則必須等待循環(huán)全部跑完才能進(jìn)入安全點,此時其他線程也必須一起等著,所以從現(xiàn)象上看就是長時間的停頓。找到了問題,解決起來就非常簡單了,把循環(huán)索引的數(shù)據(jù)類型從int改為long即可