JVM的幾點性能優化

發表于:2014-03-31來源:不祥作者:Java譯站點擊數: 標簽:性能優化
JVM的幾點性能優化 otSpot,家喻戶曉的JVM,我們的Java和Scala程序就運行在它上面。年復一年,一次又一次的迭代,經過無數工程師的不斷優化,現在它的代碼執行的速度和效率已經逼近本地編譯的代碼了。

  otSpot,家喻戶曉的JVM,我們的Java和Scala程序就運行在它上面。年復一年,一次又一次的迭代,經過無數工程師的不斷優化,現在它的代碼執行的速度和效率已經逼近本地編譯的代碼了。

  它的核心是一個JIT(Just-In-Time)編譯器。JIT只有一個目的,就是為了提升你代碼的執行速度,這也是HotSpot能如此流行和成功的重要因素。

  JIT編譯器都做了什么?

  你的代碼在執行的時候,JVM會收集它運行的相關數據。一旦收集到了足夠的數據,證明某個方法是熱點(默認是1萬次調用),JIT就會介入進來,將“運行緩慢的”平臺獨立的的字節碼轉化成本地編譯的,優化瘦身后的版本。

  有些優化是顯而易見的:比如簡單方法內聯,刪除無用代碼,將庫函數調用替換成本地方法等。不過JIT編譯的威力遠不止此。下面列舉了它的一些非常有意思的優化:

  分而治之

  你是不是經常會這樣寫代碼:

  StringBuilder sb = new StringBuilder("Ingredients: ");

  for (int i = 0; i < ingredients.length; i++) {

  if (i > 0) {

  sb.append(", ");

  }

  sb.append(ingredients[i]);

  }

  return sb.toString();

  或者這樣:

  boolean nemoFound = false;

  for (int i = 0; i < fish.length; i++) {

  String curFish = fish[i];

  if (!nemoFound) {

  if (curFish.equals("Nemo")) {

  System.out.println("Nemo! There you are!");

  nemoFound = true;

  continue;

  }

  }

  if (nemoFound) {

  System.out.println("We already found Nemo!");

  } else {

  System.out.println("We still haven't found Nemo : (");

  }

  }

  這兩個例子的共同之處是,循環體里先是處理這個事情,過一段時間又處理另外一件。編譯器可以識別出這些情況,它可以將循環拆分成不同的分支,或者將幾次迭代單獨剝離。

  我們來說下第一個例子。if(i>0)第一次的時候是false,后面就一直是true。為什么要每次都判斷這個呢?編譯器會對它進行優化,就好像你是這樣寫的一樣:

  StringBuilder sb = new StringBuilder("Ingredients: ");

  if (ingredients.length > 0) {

  sb.append(ingredients[0]);

  for (int i = 1; i < ingredients.length; i++) {

  sb.append(", ");

  sb.append(ingredients[i]);

  }

  }

  return sb.toString();

  這樣寫的話,多余的if(i > 0)被去掉了,盡管也帶來了一些代碼重復(兩處append),不過性能上得到了提升。

  邊界條件優化

  檢查空指針是很常見的一個操作。有時候null是一個有效的值(比如,表明缺少某個值,或者出現錯誤),有時候檢查空指針是為了代碼能正常運行。

  有些檢查是永遠不會失敗的(在這里null代表失敗)。這里有一個典型的場景:

  public static String l33tify(String phrase) {

  if (phrase == null) {

  throw new IllegalArgumentException("phrase must not be null");

  }

  return phrase.replace('e', '3');

  }

  如果你代碼寫得好的話,沒有傳null值給l33tify方法,這個判斷永遠不會失敗。

  在多次執行這段代碼并且一直沒有進入到if語句之后,JIT編譯器會認為這個檢查很多可能是多余的。然后它會重新編譯這個方法,把這個檢查去掉,最后代碼看起來就像是這樣的:

  public static String l33tify(String phrase) {

  return phrase.replace('e', '3');

  }

  這能顯著的提升性能,而且在很多時候這么優化是沒有問題的。

  那萬一這個樂觀的假設實際上是錯了呢?

  JVM現在執行的已經是本地代碼了,空引用可不會引起NullPointerException,而是真正的嚴重的內存訪問沖突,JVM是個低級生物,它會去處理這個段錯誤,然后恢復執行沒有優化過的代碼——這個編譯器可再也不敢認為它是多余的了:它會重新編譯代碼,這下空指針的檢查又回來了。

  虛方法內聯

  JVM的JIT編譯器和其它靜態編譯器的最大不同就是,JIT編譯器有運行時的動態數據,它可以基于這些數據進行決策。

  方法內聯是編譯器一個常見的優化,編譯器將方法調用替換成實際調用的代碼,以避免一次調用的開銷。不過當碰到虛方法調用(動態分發)的話情況就需要點小技巧了。

  先看下這段代碼 :

  public class Main {

  public static void perform(Song s) {

  s.sing();

  }

  }

  public interface Song { void sing(); }

  public class GangnamStyle implements Song {

  @Override

  public void sing() {

  System.out.println("Oppan gangnam style!");

  }

  }

  public class Baby implements Song {

  @Override

  public void sing() {

  System.out.println("And I was like baby, baby, baby, oh");

  }

  }

  perform方法可能會被調用了無數次,每次都會調用sing方法。方法調用的開銷當然是很大的,尤其像這種,因為它需要根據運行時s的類型來動態選擇具體執行的代碼。在這里,方法內聯看真來像是遙不可及的夢想,對吧?

原文轉自:http://it.deepinmind.com/jvm/2014/03/28/jvm-performance-magic-tricks.html

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97