您的位置 首页 java

java性能优化——编译器优化进阶(编译线程、内联、逃逸分析)

编译线程

当达到编译 阈值 时,方法或循环就是进入 编译队列 ,在后台异步地获取队列的代码进行编译。

编译队列 不是严格的先进先出,执行次数越多的代码具有更高的优先级。这也是在上一章节,我们通过标志 PrintCompilation 查看被编译方法时, compilationg_id 不完全按顺序递增的原因。

使用不同的编译器,在不同平台下会有不同的 线程 数,与平台的 CPU 数有关。

通常来说,使用 client 编译器,则会开启一个线程;使用server编译器,则会开启两个线程。当开启分层编译时,将会开启多个线程,在不同的平台的CPU数量下,线程数也会不同,在分层编译器中,会将client编译器称为C1编译器,将server编译器称为C2编译器,我们下满就这么称呼它们,简单列举几个情况:

cpu数量

C1

C2

1

1

1

2

1

1

4

1

2

8

1

2

16

2

6

32

3

7

64

4

8

128

4

10

我们通过标志 CICompilerCount 来查看当前 jvm 的线程数量,这是jvm处理编译队列的总线程数:

 [ root @hecs-402944 opt]# jinfo -flag CICompilerCount 11210
-XX:CICompilerCount=2
复制代码  

我的服务器是2核,所以是2。其中包含一个client线程和server线程。

内联

方法内联 是编译器当中做的最重要的性能优化。我们熟悉的java实体类,通常都会为每个属性添加getter和setter方法,这种方法的调用相比于直接访问变量,会有较大的性能开销。

jvm当中方法调用,存在于 虚拟机 栈的栈帧当中,整个调用链较长,会有较大的性能损耗。

有如下的代码:

  static  class Student {
     private  String name;

    private String firstName;
    
    private String secondName;
    
    public String getName() {
        return name;
    }

    public  void  setName(String name) {
        this.name = name;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }
}

public static void main(String[] args) {
    Student student = new Student();
    student.setName(student.getFirstName() + student.getSecondName());
}
复制代码  

在jvm帮助我们在编译时进行内联后,就会有如下的代码:

 
public static void main(String[] args) {
    Student student = new Student();
    student.name = student.firstName + student.secondName;
}
复制代码  

如上所示,已经将get和set方法的调用进行替换为直接属性的调用。

可以通过如下方式查看内联属性:

 [root@hecs-402944 opt]# jinfo -flag Inline 11210
-XX:+Inline
复制代码  

默认是开启的,此参数对性能影响巨大,不建议关闭。

内联的条件

触发方法内联是有条件的,不是所有的代码都会被内联。

是否可以被内联取决于方法代码是否 够热 和它的 大小

  • 当代码小于325字节,并且被频繁调用时,会发生内联。资料显示可以通过 MaxFreqInlineSize 设置此值,但是我在java8当中发现此值不存在。 [root@hecs-402944 opt] # jinfo -flag MaxFreqInlineSize 11210 no such flag ‘MaxFreqInlineSize’ 复制代码
  • 在上一条的基础上,只有代码小于35字节时,才会发生内联。可以通过 MaxInlineSize 设置。 [root@hecs-402944 opt] # jinfo -flag MaxInlineSize 11210 -XX:MaxInlineSize=35 复制代码

逃逸分析

逃逸分析简单来说就是检查变量,查看变量的使用位置,检测其是否被其他范围所使用。如果它没有超出范围,那就是一个 局部变量 ,我们能够针对这个变量做更多的优化。

如果被外部的方法调用,则称之为方法逃逸。 如果被外部的线程调用,则称之为线程逃逸。

在java8当中逃逸分析是默认被开启的:

 [root@hecs-402944 opt]# jinfo -flag DoEscapeAnalysis 11210
-XX:+DoEscapeAnalysis
复制代码  

开启逃逸分析后,server编译器将会进行很激进的优化:

  • 锁省略 :对于对象内部的方法锁 synchronized ,在编译时会被优化掉,对象被new()时,其基本上不会被多个线程同时调用。
  • 变量保存在 寄存器 :对于对象内的属性变量,当不会发生逃逸时,会将变量放在寄存器当中,而不是内存当中。

当然还有很多复杂的优化,这里不过多介绍,因为不建议我们针对逃逸分析做优化。

但是我们可以就逃逸分析解决一些问题:

  • 开启逃逸分析后,可能会有某些代码编译时报错,如果我们发现,最好的做法是简化这部分代码,而不是关闭逃逸分析

文章来源:智云一二三科技

文章标题:java性能优化——编译器优化进阶(编译线程、内联、逃逸分析)

文章地址:https://www.zhihuclub.com/190373.shtml

关于作者: 智云科技

热门文章

网站地图