关于java:Java开发规范之性能篇

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理