这两个例子的共同之处是,循环体里先是处理这个事情,过一段时间又处理另外一件。编译器可以识别出这些情况,它可以将循环拆分成不同的分支,或者将几次迭代单独剥离。

  我们来说下第一个例子。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编译器有运行时的动态数据,它可以基于这些数据进行决策。

  方法内联是编译器一个常见的优化,编译器将方法调用替换成实际调用的代码,以避免一次调用的开销。不过当碰到虚方法调用(动态分发)的话情况需要点小技巧了。