1. 大內存硬件上的程序部署策略
這是筆者很久之前處理過的一個案例,但今天仍然具有代表性。一個15萬PV/日左右的在線文檔類型網站最近更換了硬件系統,服務器的硬件為四路志強處理器、16GB物理內存,操作系統為64位CentOS5.4,Resin作為Web服務器。整個服務器暫時沒有部署別的應用,所有硬件資源都可以提供給這訪問量并不算太大的文檔網站使用。軟件版本選用的是64位的JDK5,管理員啟用了一個虛擬機實例,使用-Xmx和-Xms參數將Java堆大小固定在12GB。使用一段時間后發現服務器的運行效果十分不理想,網站經常不定期出現長時間失去響應。
原因:垃圾收集器停頓所導致的。默認使用的是吞吐量優先收集器,回收12GB的Java堆,一次FullGC的停頓時間就高達14秒。
解決方案:
目前單體應用在較大內存的硬件上主要有兩種部署方式
-
通過一個單獨的Java虛擬機實例來管理大量的Java堆內存。
-
同時使用若干個Java虛擬機,建立邏輯集群來利用硬件資源。
其次:可以在深夜執行定時任務的方式觸發full GC甚至是重新啟動應用服務器來保持內存可用空間在一個穩定水平。
控制Full GC頻率的關鍵是老年代的相對穩定,這主要取決于應用中絕大多數對象能否符合“朝生夕滅”的原則,即大多數對象的生存時間不應當太長,尤其是不能有成批量的、長生存時間的大對象產生,這樣才能保障老年代空間的穩定。
現實生活中:B/S形式的應用里,多數對象的生存周期都應該是請求級或者頁面級的,會話級和全局級的長生命對象相對較少。
2. 集群間同步導致的內存溢出
集群的優點可以均衡并發處理,以及合理利用服務器資源。缺點就是會使用一部分開銷,以及數據冗余。
3. 堆外內存導致的溢出錯誤
直接內存不能像新生代、老年代那樣,發現空間不足了就主動通知收集器進行垃圾回收,它只能等待老年代滿后Full GC出現后,“順便”幫它清理掉內存的廢棄對象。否則就不得不一直等到拋出內存溢出異常時,才進行清理。
從實踐經驗的角度出發,在處理小內存或者32位的應用問題時,除了Java堆和方法區之外,我們注意到下面這些區域還會占用較多的內存,這里所有的內存總和受到操作系統進程最大內存的限制:
直接內存:可通過-XX: M axDirectM emory Size調整大小,內存不足時拋出OutOf-M emory Error或者OutOfMemoryError: Direct buffer memory。
線程堆棧:可通過-Xss調整大小,內存不足時拋出StackOverflowError (如果線程請求的棧深度大于虛擬機所允許的深度)或者OutOfM emory Error (如果Java虛擬機棧容量可以動態擴展,當棧擴展時無法申請到足夠的內存)。
Socket緩存區:每個Socket連接都Receive和Send兩個緩存區,分別占大約37KB和25KB內存,連接多的話這塊內存占用也比較可觀。如果無法分配,可能會拋出IOException: Too many open files 異常。
JNI代碼:如果代碼中使用了JNI調用本地庫,那本地庫使用的內存也不在堆中,而是占用Java虛擬機的本地方法棧和本地內存的。
虛擬機和垃圾收集器:虛擬機、垃圾收集器的工作也是要消耗一定數量的內存的。
4. 外部命令導致系統緩慢
用戶根據建議去掉這個Shell腳本執行的語句,改為使用Java的API去獲取這些信息后,系統很快恢復了正常。
5. 服務器虛擬機進程崩潰
原因:MIS系統的用戶多,待辦事項變化很快,為了不被0A系統速度拖累,使用了異步的方式調用Web服務,但由于兩邊服務速度的完全不對等,時間越長就累積了越多Web服務沒有調用完成,導致在等待的線程和Socket連接越來越多,最終超過虛擬機的承受能力后導致虛擬機進程崩潰。
解決方案:通知OA門戶方修復無法使用的集成接口,并將異步調用改為生產者/消費者模式的消息隊列實現后,系統恢復正常。
6. 不恰當數據結構導致內存占用過大
我們具體分析一-下HashM ap空間效率,在HashM ap<Long,Long結 構中,只 有Key和Value所存放的兩個長整型數據是有效數據,共16字節(2x8字節)。這兩個長整型數據包裝成java.langLong對象之后,就分別具有8字節的M ark Word、 8字節的Klass指針,再加8字節存儲數據的long值。 然后這2個Long對象組成Map .Entry之后,又多了16字節的對象頭,然后一個8字節的next字段和4字節的int型的hash字段,為了對齊,還必須添加4字節的空白填充,最后還有HashM ap中對這個Entry的8字節的引用,這樣增加兩個長整型數字,實際耗費的內存為( ong(24byte)x 2)+Fntry(32by te)+HashMap Ref(8byte)=88byte,空間效率為有效數據除以全部內存空間,即16字節/88字節=18%,這確實太低了。