共计 2537 个字符,预计需要花费 7 分钟才能阅读完成。
JAVA 开发中,大部分的性能问题起因并不在于 JAVA 语言自身,而是咱们用这些语言写的程序,所以养成良好的编码习惯十分重要。
上面给大家分享一些日常开发中比拟常见的典型案例:
一. 类中的外部办法申明为 private
很多同学感觉这个无所谓,写代码时喜爱一个类里的所有办法都是 public
的(起因大家都懂),美其名曰:便于前期扩大。。
对于不须要内部拜访的办法改为公有的,不仅仅是因为面向对象的思维,合乎数据封装和平安拜访准则,还有一个很大的益处就是 任何 private 办法都是隐性的 final!
Any private methods in a class are implicitly final.
《Think In Java》
摘自《Think In Java》第 6 章
被 final
润饰的办法可能减少内联 inline 的可能性
办法内联就是把调用方函数代码 ” 复制 ” 到调用方函数外部,作为本人的一部分代码执行,缩小因函数调用产生的开销,即 JIT 优化。
伪代码如下:
public int sum(int a, int b, int c, int d){return add1(a, b) + add2(c, d);
}
private int add1(int a, int b){return a + b;}
private int add2(int c, int d){return c + d;}
内联后的代码:
public int sum(int a, int b, int c, int d){return a + b + c + d;}
为什么办法内联能晋升性能呢?
大家都晓得函数调用其实就是对栈 stack
的操作,即压栈和出栈过程,当一个办法被调用,一个新的栈帧会被加到栈顶,调配的本地变量和参数会存储在这个栈帧,而后跳转到指标办法代码执行,办法返回的时候,本地办法和参数被销毁,栈顶被移除,最初返回到原来的地址执行。
所以函数调用须要有肯定的工夫和空间开销,当一个办法体不大,但又频繁被调用时,这个工夫和空间开销会绝对变得很大,这样就变得十分不划算,势必会升高程序的性能。依据二八准则,80% 的性能耗费其实是产生在 20% 的代码上,对热点代码的针对性优化能够晋升整体零碎的性能。
但触发办法内联是有条件的,不是说加了 final
润饰就能够立刻触发内联,还须要依据 JVM 的参数:-XX:CompileThreshold
判断编译次数,-XX:MaxFreqInlineSize
被内联办法体的大小限度。
所以说能够应用办法内联的业务场景是:
对于频繁调用的热点办法,并且办法体不大的,倡议应用 final
润饰 (private
办法会隐式地被指定 final
修饰符,所以无须再为其指定 final
修饰符)。
二. public 办法倡议也尽量指定 final 修饰符
基于下面第一条,满足上述业务场景的办法,起因同上,利于 JIT 优化。当然也能够间接润饰办法所在的类上,这样 final
的类不容许被继承,而且该类的所有办法默认都是 final
的。
这里补充一条限度条件:并且没有被 Spring AOP 代理的办法
三. 可能应用 lambda 表达式的中央就不要用匿名外部类实现
lambda 表达式不仅仅是语法糖,它编译后的 class 文件在 jvm 中执行的指令也是有区别的,应用的指令是invokeDynamic,相比于匿名外部类的调用上开销会更小一些,因为它没有匿名外部类的初始化过程,代码上也更简洁。
匿名外部类写法:
private static final ExecutorService thredPool = Executors.newCachedThreadPool();
thredPool.submit(new Runnable() {
@Override
public void run() {// 业务逻辑}
});
lambda 表达式写法:
private static final ExecutorService thredPool = Executors.newCachedThreadPool();
thredPool.submit(() -> {// 业务逻辑});
四. 尽量不要在办法中频繁调用全局变量
在类中,成员变量 (全局变量) 保留在堆 Heap 中,函数外部的局部变量 (根本类型、参数、对象的援用) 都保留在栈 Stack 中,在函数外部拜访本人的这些变量必定要比拜访函数内部的变量速度要快,所以从栈中操作堆中的数据速度会比较慢。
代码示例如下(反例):
private int result = 0; // 成员变量
public void sum(int... i){for (int j : i) {result += j; // 频繁操作函数内部的成员变量 result}
}
批改后的代码:
private int result = 0; // 全局变量
public void sum(int... i){
int temp = 0; // 长期变量
for (int j : i) {temp += j; // 操作函数外部的长期变量}
result = temp; // 缩小对成员变量的拜访
}
五. 优化汇合操作中先 contains 再 get 的写法
咱们的代码中常常会遇到要判断汇合中是否存在这个元素,存在再取值的业务场景,伪代码如下:
public void setOrderPrice(Order order, Map<String, Price> map){if(map.containsKey(order.getId())){order.setPrice(map.get(order.getId()));
} else {order.setPrice(new Price());
}
}
其实能够间接调用 get 获取,而后判空,这样就省去了一次查找匹配的过程,批改后如下:
public void setOrderPrice(Order order, Map<String, Price> map){Price price = map.get(order.getId(); // 间接调用 get
if(price != null){order.setPrice(price);
} else {order.setPrice(new Price());
}
}
以上仅是作者这些年在 Java 开发性能方面的工作见解,性能优化须要在工夫、效率、可读性各方面衡量,做出取舍,不要把下面的内容当成教条,活学活用,变通为宜。
文章起源:http://javakk.com/197.html