遷移 64 位虛擬機未必性能更好
業務量上升以後,需要使用的內存隨之增加,而在通常 32 位系統上,單個進程佔用的最大內存通常是 2GB,且考慮到堆外內存的使用,32 位機器可能無法滿足內存要求,一種常見的應對方式就是換用 64 位服務器。而對於 Java,由於指針膨脹和字節對齊,同一個程序在 64 位虛擬機上佔用的內存會多於 32 位虛擬機。開發者換用 64 位虛擬機後,很可能會增加虛擬機的堆大小,而這將導致 Full GC 垃圾回收的時間大大增加,導出堆快照也變得困難。
因此,使用 64 位虛擬機增加內存時,需要特別注意對內存的使用,儘量不要觸發 Full GC 導致長時間停頓。另一種方法是建立 32 位虛擬機的集群來提高性能。
堆外內存溢出
Java 本地方法調用,如調用 C++ 實現的本地模塊、NIO 的 DirectByteBuffer,都會佔用大量內存。而在開發中,開發者往往重點關注了堆內存的大小,在內存溢出時也傾向於增加堆內存,而忽視了堆外內存的使用。堆外內存並不會像堆內存一樣不足馬上通知 GC 進行垃圾回收,堆外內存只能等待老年代空間不足進行 Full GC 時順便回收內存,否則堆外內存只能等到空間不足時拋出內存溢出異常,然後請求 GC 進行回收。
因此,配置虛擬機,除考慮常規的堆大小外,優化時還需要考慮 Direct Memory、線程棧、socket 緩衝區、JNI 代碼、虛擬機、GC 佔用的內存大小。
外部命令的時間消耗
Java 開發中如果需要執行 shell 腳本,可以使用 Runtime.getRuntime().exec 方法,還能從返回的 Process 對象中讀取標準輸出、錯誤輸出、等待執行結束。根據方法註釋,該方法首先複製當前進程產生一個子進程,在子進程中執行命令,結束後退出子進程。
進程的複製比較消耗 CPU 和內存,應儘量通過 Java 程序本身去完成相關功能。
多線程使用線程池
Java 虛擬機中沒有給用戶用的多進程方法,並行處理更多地使用多線程方式。默認情況下,Linux 限制用戶的線程數量上限為 1024,當然包括了系統中運行的所有線程。通常情況下,線程資源不會被耗盡,但多線程程序如果頻繁創建新線程也會遇到線程資源不足的情況。一方面,可以調整系統設置,提高線程數上限,另一方面,應儘量避免頻繁創建線程。線程雖小,創建時一樣要消耗時間和內存。
多線程程序應儘量採用 Java 的線程池,這樣線程的個數總體可控,使用時可以避免創建線程的時間消耗。Java 提供了多種功能強大的線程池類型,基於線程池可以對任務進行緩存、按照一定的時間頻率執行任務、返回執行結果、分叉與合併等。
每週 3 篇學習筆記或技術總結,面向有一定基礎的 Java 程序員,內容涉及 Java 進階、虛擬機、MySQL、NoSQL、分佈式計算、開源框架等多個領域。關注作者或微信公眾號 backend-develop 第一時間獲取最新內容。