JAVA性能優化總結


  我們知道JAVA語言與C語言的其中一個區別就是JVM中有垃圾回收器可以通過對運行中的對象進行判斷是否存活並且將在內存中已經不在使用的對象進行回收釋放其所占用的內存,而C語言需要進行手動的釋放內存,1個對象的創建使用釋放都需要程序進行顯式的操作。當然不管是C還是JAVA都有自己適合的開發領域。
  對於代碼性能優化,對於項目前期由於前期數據量並不是太大但是隨着時間的推移數據量的激增,如果沒有良好的編碼習慣后期會帶來較大的性能開銷,故項目初期編碼過程應養成良好的習慣避免后期繁重的codereview等。
  本篇博客將筆者在工作過程中遇到的性能瓶頸以及在其中是如何進行優化進行記錄,以便后續有相關需求的小伙伴進行采納借鑒。


對象篇

  在JMM中(JAVA內存模型)中保存對象的開銷其實是相當大的,並且JMM要求對象必須按照8個字節對齊,雖然JMM會提供對字段的重排序來避免用戶隨意指定對象字段的順序以此來嘗試各個字段重排達到降低整體的對象開銷。但是即使是這樣1個空的String對象仍然得占用 對象頭(8 字節)+ 引用 (4 字節 ) + char 數組(16 字節)+ 1個 int(4字節)+ 1個long(8字節)= 40 字節 如果在程序過程中產生大量的對象無疑會加重GC的工作。因此在實際編碼過程中應該避免創建大量的對象,在不影響代碼可讀性及程序並發問題時,應該盡可能復用已有的對象,減輕GC的壓力。

  1. 使用StringBuild/StringBuffer代替循環體內進行字符串拼接,StringBuild與StringBuffer底層會默認創建一個16char數組進行字符串緩存,等到需要時在創建出String對象,而不是每一次都進行String對象創建。
  2. 使用StringTokenizer 代替Split 做字符串切割(如果不涉及到復雜正則只是簡單字符串切割)
  3. JAVA編譯時會對常量拼接進行重新定義作為一個完整的常量故無需糾結性能問題,可見如下程序塊。
final String aa = "a"
final String bb = "b"
System.err.println((aa + bb) == (aa + bb));//true
  1. JDK1.6前后的subString方法實現
      在JDK1.6 subString內部實現是子String仍然會保留父String的char數組引用,這在一定程度上會造成內存泄露,特別是當有特別長的字符串實際上只需要其特別短的字符串,但是由於引用依賴,GC無法回收,造成父String一直滯留在JVM內存中無法回收。
      JDK1.6之后subString內部對其優化,調用拷貝父String的char數組中的子char數組形成一份新的副本,如此子String與父String之間不存在依賴關系,GC能夠對不在使用的父String進行GC回收。
  2. 循環遍歷中如果存在重復創建大量相同的字符串,建議創建緩存池進行對象緩存。
  3. 避免使用正則表達式,如必要使用至少要把Pattern進行緩存,避免反正創建Pattern編譯。
  4. 當需要對一個基本數據類型進行字符串轉換應該盡可能使用toString或者String.valueOf(obj) 替換 obj+""。
  5. 在進行大文本字符串拼接時,應該為StringBuffer,StringBuilder設置初始化容量值

JVM篇

  1. -Xms-Xmx設置相同,避免每次垃圾回收完成后JVM重新分配內存。
  2. 原則上應該避免太大深度的遞歸,畢竟遞歸越深其中間棧幀所產生的數據引用仍然有效,無法被GC清除。
  3. 當虛擬機棧中需要存儲基本數據或對象引用時需要調整-Xss來避免發生StackOverflowError異常
  4. 在虛擬機棧中隨着棧幀的pop會對棧幀內的內存進行及時的清理,所以在局部方法內部中其中間變量應該盡可能使用8大基本數據類型才能夠隨着棧幀結束而立即內存回收。
  5. 為了避免頻繁觸發JVM對基本數據類型進行拆包與裝包操作,其中間變量應該盡量使用基本數據類型而不是其包裝類型。
  6. JDK默認只緩存-128~+127的Integer和Long 如果超出該范圍則會創建出新的對象,如果對計算數據敏感可是適當通過-XX:AutoBoxCacheMax增大緩存范圍。
  7. G1與CMS收集器的選擇,G1收集器會對堆內內存進行划分成Region,其堆越小則划分的Region數也會越少,在小堆的表現並不會比CMS突出。筆者認為8G以下采用CMS比較合適。目前比較期待Java 11 新加入的ZGC號稱可以達到10ms 以下的 GC 停頓。
  8. 通過-XX:+AlwaysPreTouch提前初始化好真正的物理內存,而不是需要才進行內存申請初始化。默認未添加該參數時而-Xms、-Xmx只是告訴告訴操作系統需要多少內存,從而避免被其他進程使用,而只有當正直使用時才會進行內存逐漸申請。比如在堆中Eden區進行對象創建又或者Young區轉Old區的內存空間。不過該參數也會影響啟動時長,隨着堆內存越大啟動時間也會增大。
  9. JDK監控工具 Jconsole,jProfile,VisualVM 可以通過可視化界面查看JAVA堆內存使用情況進行判斷是否需要進行代碼優化。

線程篇

  1. 如何為線程池選擇合適線程數?
      線程任務一般可以分為計算密集型IO密集型
      計算密集型CPU處於忙碌中,此時需要做內存數據讀寫計算,沒有任務的阻塞狀態。而IO密集型任務,在執行IO時堵塞,CPU處於等待狀態,等待過程中系統會將時間片分給其他線程處理。
      計算密集型任務線程數最好與系統核心數掛鈎,畢竟4核單線程主機在某一時刻只能同時跑4個線程,如果過多的線程數反而會因為切換上下文而耗費更多的任務時間,可以通過調用JDK自帶的方法Runtime.availableProcessors獲得系統支持的可以核心數。N+1。
      對於IO密集型,合適的線程數可以獲得良好的性能支持,系統通過將IO阻塞的任務線程的時間片交給未被阻塞的任務線程,通過合適的調度發揮出比單線程更好的性能支持。在選擇線程數應該考慮內存支持程度,避免過多的線程數導致內存激增產生OOM。2N+1。
      線程等待時間所占比例越高,需要越多線程。線程CPU時間所占比例越高,需要越少線程。
      一個完整的系統不應只有一個線程池,應該對線程任務進行梳理分類,划分出各自的任務類型以及工作負載來提供多個線程池。
  2. 如果需要加鎖竟可能使用加鎖代碼塊將鎖范圍控制在最小范圍中,而不是在方法體中加鎖。
  3. 盡量避免嵌套取鎖,容易造成死鎖問題。
  4. 合理時候使用讀寫鎖來替換synchronized獨占鎖。
  5. JDK1.8流處理對於大量需要計算的數據時可采用parallelStream進行並發數據處理,可提高處理速度。
  6. 多了解各類對象對並發支持性例如HashMap、SimpleDateFormat等。
  7. 合理使用ThreadLocal來實現數據在線程本地化。
  8. 如果對執行過程沒有嚴格的串行順序可以采用FutureTask對計算結果統一取值拼裝。

雜談篇

  1. 為避免循環之間切換,盡量采用小循環嵌套大循環。
  2. 避免在循環中大概率拋出異常的代碼塊進行TryCatch,盡可能放在循環外層TryCatch。
  3. 代碼塊中應盡量使用懶加載,即需要時才進行加載,不需要則不加載。
  4. 在JVM級別做適當的緩存級別,可以使用EhCache、Guava Cache。Ehcache適合支持持久化功能,有集群解決方案,而Guava Cache只是一個支持LRU的concurrentHashMap,沒有Ehcache那么多特性,只支持增刪改查,刷新規則和時效規則設定等最基本的設定。
  5. 設置日志輸出級別,避免大量無關緊要的日志輸出,影響業務系統性能。
  6. SQL調優可以通過索引分析,減少查詢字段,限制表讀數據等進行分析調優。
  7. 數據庫瓶頸可以通過讀寫分離減少單服務器壓力,以及通過垂直拆分或水平拆分將數據表數據合理治理。
  8. 引入緩存架構減輕數據庫壓力。

哪些是性能瓶頸的關鍵點

  1. 有些任務需要大量的計算,需要不停地占用CPU資源,導致其他任務搶占CPU的能力變弱而導致響應速度下降,而帶來的性能問題。比如任務內過多的重復計算,無限的自循環計算,JVM頻繁的FULL GC,多線程做大量的上下文切換等。
  2. 一般來說說內存的讀寫速度非常快,一般不存在性能問題,但是由於內存成本比硬盤高即內存空間是有限的,應該注意內存的范圍避免應內存耗盡而導致OOM等問題。
  3. 磁盤IO是引起系統性能的一大因素。涉及到數據落地等問題應盡量使用硬盤順序讀寫而不是隨機讀寫,順序讀寫的讀寫能力比隨機讀寫能力強大太多。
  4. 數據庫方面除了必要的SQL優化減少查詢時間外如有必要需要引入緩存層減輕數據庫壓力
  5. 合理使用鎖減少並發時造成性能損耗
  6. 防止瞬間時拋出大量的異常,拋出異常會不停從堆棧內拔取異常信息,非常耗性能。

衡量性能的指標

  1. 系統響應時間
      從發送請求到接收到數據時所需要的時間
  2. 系統吞吐量
      單位時間內成功地傳送數據的數量大小
  3. 負載承受能力
      隨着並發量的增加最終導致系統拋出大量的異常,整個系統處於不可用的時候。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM