歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

開發高性能Java應用程序基礎

雖然Java的垃圾回收和當前高配置的服務器可以讓程序員大部分時間忘掉OutOfMemoryError的存在,但是訪問量增大後頻繁的GC會額外消耗CPU (使用top查看結果為us值高),系統響應速度下降,積壓的請求又會占用更多內存從而惡性循環,嚴重時可能導致系統不斷Full GC造成應用停頓。

優化內存的使用可從以下幾方面著手:

一、節流

1 使用單例模式

單例模式是開發者最早接觸並使用的設計模式之一,盡管寫代碼的時候可能還不知道用了設計模式。簡單來說就是構造函數private化,通過靜態方法獲得唯一實例。因為其特性,對於某些場景例如每次請求都要使用無狀態工具類的檢驗方法,使用單例模式可以大量節省創建新對象的開銷。

public class Singleton {

 private Singleton() {}
 
 private static Singleton instance = new Singleton();
 
 public static Singleton getInstance() {
  return instance;
 }
 
 public void doSomething() { }
}

2 緩存常用對象

簡單來說就是按一定特征創建"對象緩存池",使用集合類保存已創建的對象,當有相同特征的對象申請時,使用緩存池中現有的對象代替通過 new關鍵字重新創建。

public class BigObjectPoolTest {
 public static void main(String[] args) {
  long start = System.nanoTime();
  for(int i = 0; i < 10000; i++) {
   BigObjectPool.getBigObject("xxx", true);
  }
  System.out.println("使用緩存池耗時" + TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) + "毫秒");
  start = System.nanoTime();
  for(int i = 0; i < 10000; i++) {
   BigObjectPool.getBigObject("xxx", false);
  }
  System.out.println("不使用緩存池耗時" + TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) + "毫秒");
 }
}

class BigObjectPool {
 private static Map<String, BigObject> map = new HashMap<String, BigObject>();
 
 static {
  map.put("xxx", new BigObject("xxx"));
  map.put("yyy", new BigObject("yyy"));
 }
 
 public static BigObject getBigObject(String key, boolean usePool) {
  if(usePool) {
   BigObject bo = map.get(key);
   if(bo == null) {
    bo = new BigObject(key);
   }
   return bo;
  } else {
   return new BigObject(key);
  }
 }
}

class BigObject{
 private String name;
 private byte[] data = new byte[1024 * 1024];
 public BigObject(String name) { this.name = name; }
}

以-Xms32m -Xmx32m -Xloggc:d:/gc.log 參數運行

使用緩存池耗時3毫秒
不使用緩存池耗時998毫秒

(查看gc.log,可以觀察到不使用緩存池觸發Minor GC 1000次以上)

實際業務中通常使用EhCache等框架代替自己實現緩存池。

與這種實現原理相似的也有一個設計模式:享元模式,區別是享元模式更關注類設計結構上的優化,對上下文環境的設計也做了明確定義。

3 避免設計過大的對象

如果業務模型中要求的類的屬性和方法都非常多,可以嘗試將其拆分成多個小類,再通過合成/聚合模式組裝成一個大類,這也符合設計模式的優化思想。甚至可以結合上面的對象緩存池的方式將其中一部分內容緩存化。

class Composition {
 private BigObject bigObject = null;
 private int id;

 public void setBigObject(BigObject bigObject) {
  this.bigObject = bigObject;
 }

 public Composition(int id) {
  this.id = id;
 }
}

Composition c = new Composition(1);
c.setBigObject(BigObjectPool.getBigObject("xxx", true));

4 一些小技巧

使用StringBuilder代替用+號連接字符串

盡量使用int, long等基本類型代替Integer, Long包裝對象

合理利用SoftReference和WeakReference

二、開源 - 調整虛擬機參數

一般設置 java -server -Xms2048m -Xmx2048m -XX:PermSize=256m  -XX:MaxPermSize=256m

-Xms和-Xmx決定java堆區可使用的內存最小值和最大值,通常設為相同的值,避免運行期間反復的重新申請內存。如果出現OutOfMemoryError: Java heap space,則在硬件允許的情況下臨時調大-Xmx,為排查問題和優化代碼爭取時間。

-XX:PermSize和-XX:MaxPermSize決定永久代可用空間大小,存放class和meta信息,通常設置為相同的值。如果出現OutOfMemoryError: PermGen space,說明加載的類和jar文件過多,可以調大這兩個參數值。

如果web容器下有多個應用引用了相同的第三方jar文件,可以轉移到容器的共享目錄。

另一個重要參數是-Xmn,決定堆區新生代的大小,通常占-Xmx的比值設置為1/4到1/3。如果業務中有大量體積大且生命周期很短的對象創建需求,可適當調大新生代空間以利於失效對象在新生代中被回收。

此外,可通過參數設置回收算法:

–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

回收算法的選擇和對比需要較大的篇幅介紹,這裡不做詳細的解釋。通常來說,對於響應時間優先的web應用,ConcMarkSweepGC(CMS)是個不錯的選擇。

需要注意的是,經過幾代發展後,JVM對內存管理已經做的非常好。如果不是有明確的證據證明JVM的默認選擇不合理,就沒必要做過多細節的調整設置。調整後,可通過-XX:+PrintGCDetails -XX:+PrintGCTimeStamps等參數輸出GC信息進行比對,優化的首要目標是減少Full GC次數和時間。

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2017-01/139410p2.htm

Copyright © Linux教程網 All Rights Reserved