性能主题

Java性能优化要点

  本文介绍如何通过以下几点从Java中挤压出性能,该大部分经验来自于Netty作者。

 

JITJava即时编译器

当Java执行runtime环境时,每遇到一个新的类,JIT编译器在此时就会针对这个类别进行编译(compile)被优化成相当精简的原生型指令码(native code),会做一下工作:

  • 展开循环loop-unrolling
  • 重新安排代码
  • 移除同步synchronized
  • 优化锁
  • 内联热点方法

首先,JIT会展开我们代码中的循环语句,所以,我们编码时尽量注意不要在关键热点部分编写让JIT难于展开的循环语句。

JIT比较难以展开的循环语句如下:

int i = 0;

for (;;) {

  if (array.length == i) {

    break;

  }

  doSomething(array[i++]);

}

这种for循环虽然编写方便,但是JIT不喜欢,下面循环则易于JIT展开:

int i = 0;

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

  doSomething(array[i]);

}

其次,JIT会内联一些热点小方法代码,这些小方法缺省差不多是325字节。比如下面是普通代码:

public void methodA() {

  ... // Do some work A

  methodB();

}

 

private void methodB() {

  ... // Do some more work B

}

JIT会将methodB内联合并到methodA中

//采取methodB内联到到methodA

public void methodA() {

  ... // Do some work A

  ... // Do some more work B

}

可以通过下面的Java运行配置记录检测内联:

java
-XX:+PrintCompilation
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
.... > inline.log

PrintCompilation:当JIT编译发生输出打印
UnlockDiagnosticVMOptions:这是标识 -XX:+PrintInlining需要的
-XX:+PrintInlining :当方法被内联后打印出来

内联日志inline.log效果如下:

@ 42 io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe::read (191 bytes) inline (hot) (这表示方法hot被内联了)
@ 42 io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe::read (327 bytes) hot method too big (但是方法hot用于内联太大了)
@ 4 io.netty.channel.socket.nio.NioSocketChannel::config (5 bytes) inline (hot)
@ 1 io.netty.channel.socket.nio.NioSocketChannel::config (5 bytes) inline (hot)
@ 12 io.netty.channel.AbstractChannel::pipeline (5 bytes) inline (hot)

我们编码时对于热点方法不要编写对内联太大的方法,如下面read方法:

private final class NioMessageUnsafe extends AbstractNioUnsafe {

  public void read() {

    final SelectionKey key = selectionKey();

    if (!config().isAutoRead()) {

      int interestOps = key.interestOps();

      if ((interestOps & readInterestOp) != 0) {

        // only remove readInterestOp if needed

        key.interestOps(interestOps & ~readInterestOp);

      }

    }

    ... // rest of the method

  }

  ...

}

分解出read() 方法一部分代码到新的方法中:

private final class NioMessageUnsafe extends AbstractNioUnsafe {

  public void read() {

    if (!config().isAutoRead()) {

      removeReadOp();

    }

 

  private void removeReadOp() {

    SelectionKey key = selectionKey();

    int interestOps = key.interestOps();

    if ((interestOps & readInterestOp) != 0) {

      // only remove readInterestOp if needed

      key.interestOps(interestOps & ~readInterestOp);

    }

  }

 

    ... // rest of the method

  }

  ...

注意到read方法从原来多行已经变成了简单几行,这时我们再看看JIT的内联日志:

@ 42 io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe::read (288 bytes) inline (hot)

只有一行输出,说明read方法已经小到适合内联了。

下一页

Netty原理与使用