Java 程序性能如何調優?技巧篇

搬磚者:為什麼程序總是那麼慢?它現在到底在幹什麼?時間都花到哪裡去了?


面試官:簡單談談 Java 程序性能優化?


1.字符串處理優化,乃優化之源。


研發過程中,String 的 API 用的應該是最多,創建 String 對象,以及字符串分割處理那是常有的事兒。


1.1. 字符串分割,誰更勝一籌?


字符串分割,常用的方式有哪些?哪種方式好一些?


方式一,經常用 String 提供的 split() 方法來滿足業務需求。


Java 程序性能如何調優?技巧篇

代碼模擬了一些數據,然後程序跑起來,花費大約 3000 多毫秒。


方式二,採用字符串分割的工具類 StringTokenizer。


Java 程序性能如何調優?技巧篇


採用 StringTokenizer 完成 split() 同樣的數據分割,花費大約 500 毫秒。


從運行效果, StringTokenizer 其效率高於 split() 方法。所以,在能夠使用 StringTokenizer 進行處理的地方,就儘量使用 StringTokenizer 進行字符串分割處理。


另外,平時研發中,需要注意一點,在使用索引訪問用 String 的 split() 方法得到的數組時,需做最後一個分隔符後有無內容的檢查,否則會有拋異常的風險。


Java 程序性能如何調優?技巧篇


1.2. 字符串拼接,哪種方式更優?


方式一,使用 + 號拼接字符串。


Java 程序性能如何調優?技巧篇


程序跑起來,大約花費 27687 毫秒。


方式二,使用 StringBuilder 進行拼接字符串。


Java 程序性能如何調優?技巧篇


程序跑起來,大約花費 24 毫秒。


很顯然,使用 + 號拼接字符串,其效率明顯較低,採用 StringBuilder 來完成字符串連接性能相對較好,同理,如果需要考慮線程安全的情況下,可以選擇 StringBuffer。


另外,在阿里開發手冊中也強烈推薦,在循環體內,字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。如果採取 + 號拼接,會造成內存資源浪費。


2.善用 arraycopy(),讓數組複製不再難。


數組複製是研發過程中,使用較多的功能,JDK 中提供了 API 來實現。但是,哪種方式較好呢?


方式一,作為開發人員,沒事就喜歡造輪子,代碼會這麼寫。


Java 程序性能如何調優?技巧篇


方式二,採取 System.arraycopy() 來完成數組複製。


Java 程序性能如何調優?技巧篇


紅色圈住部分,在本機跑起來驗證,方式一、方式二,差距不是特別大,在數據量大的時候,System.arraycopy() 還是稍微好一點。


方式三,採取 Arrays.copyOf() 來完成數組的複製。


Java 程序性能如何調優?技巧篇


鑑於 Arrays.copyOf() 底層還是調用 System.arraycopy() 來實現,性能肯定稍遜色於直接調用 System.arraycopy() 來完成數組複製。


所以,對數組的操作,如果能用 System.arraycopy() 這個方法實現,建議儘量去使用。


3.關注循環體,別做重複勞動。


儘可能讓程序少做重複的計算,尤其要重點關注循環體內的代碼。


Java 程序性能如何調優?技巧篇


舉個簡單的栗子,上面代碼段中,Math.PI * Math.sin(k) 的執行在循環體中重複執行,而且執行結果是唯一的,可以考慮提到循環體外。


Java 程序性能如何調優?技巧篇


本機進行驗證時,前者大約花費 30 毫秒,而調整後,大約花費 2 毫秒,性能提升還是有的。


所以,從循環體內提取重複的代碼,可以有效的提升系統性能。


另外,在阿里開發手冊中強烈推薦,循環體中的語句要考量性能,以下操作儘量移至循環體外處理,如定義對象、變量、獲取數據庫連接,進行不必要的 try-catch 操作(這個 try-catch 是否可以移至循環體外)。


4.集合用的多,使用場景要注意。


業務研發中,集合家族的 API 使用頻率相當之高。那麼,充分的選擇好數據結構進行數據存儲,便是最好的程序優化


為了更清晰的說清各自的使用場景,也為了更好的助你掌握,梳理成思維導圖。


4.1. List 家族,誰能得寵?


Java 程序性能如何調優?技巧篇


4.2. Map 家族,誰佔鰲頭?


Java 程序性能如何調優?技巧篇


另外,在集合初始化時,要指定集合初始值大小。

說明:HashMap 使用 HashMap(int initialCapacity) 初始化。


正例:

initialCapacity = (需要存儲的元素個數 / 負載因子) + 1。

注意負載因子(即 loader factor)默認為 0.75,如果暫時無法確定初始值大小,請設置為 16(即默認值)。


反例:HashMap 需要放置 1024 個元素,由於沒有設置容量初始大小,隨著元素不斷增加,容 量 7 次被迫擴大,resize 需要重建 hash 表,嚴重影響性能。


—— 阿里開發手冊


4.2. Set 家族,誰最適配?


Java 程序性能如何調優?技巧篇


圖中已經把常用的數據結構列舉了出來,就不再一一去寫代碼驗證啦。還是那句話,選擇好數據結構進行數據存儲,便是最好的程序優化。


5.緩衝,讓子彈飛一會兒。


緩衝,最常用的場景就是提高 I/O 的速度,解決 I/O 性能瓶頸。在

Java 中對不少 I/O 組件都提供了緩衝功能。


例如,採用 FileWriter 向文件中寫入數據。


Java 程序性能如何調優?技巧篇


程序跑起來,花費大約 8212 毫秒。那麼,再來看看加入緩衝之後會有什麼效果?


Java 程序性能如何調優?技巧篇


程序跑起來,花費大約 4143 毫秒,性能提升了一倍。


所以,文件讀寫操作時儘量都使用緩衝流進行操作,有助於提升性能。


6.緩存,讓坐飛機的和坐驢車的打交道。


在實際項目研發中,緩存也是經常使用到,緩存是為了提升系統性能而開闢的內存空間。


最為簡單的緩存可以直接使用 HashMap 實現,例如應用的配置信息,在啟動的時候都加載進去。


針對銀行編碼等一些使用頻率較高的業務數據,或者來之不易的計算結果,都可以保存到緩存中,當再次使用時,直接從緩存中獲取,而不需要再佔用寶貴的系統資源。


目前有很多基於 Java 的緩存框架,而我用的最多的是 EhCache。


7.日誌記的好,線上沒煩惱。


推薦:謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info 日誌;如果使 用 warn 來記錄剛上線時的業務行為信息,一定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。


說明:大量地輸出無效日誌,不利於系統性能提升,也不利於快速定位錯誤點。

記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能給問題排查帶來好處?

—— 阿里開發手冊


8.雜七雜八,囉嗦一句不嫌多。


一個優化原則。先實現業務功能,再考慮優化性能,如果功能都沒實現,談其它的都白扯。

一個調優思路。

  • 首先從系統設計層面,去看看是否有改進的可能,是不是可以引入一些設計模式、是不是可以引入緩存機制等方法,來屏蔽潛在的性能問題。
  • 然後從代碼層面,看看代碼是否有優化的可能。
  • 接著去看看 Java 程序運行的環境,也就是通過調整 JVM 的參數來提升一下性能。
  • 接著到數據庫層面,看看是否有調優的可能。
  • 最後到操作系統層面,看看是否可以進行調優。


Java 程序性能如何調優?技巧篇

好了,本次的分享,就到這裡,希望你們喜歡,更多精彩歡迎關注「一猿小講」。


分享到:


相關文章: