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方法已经小到适合内联了。