java代码编译-java代码编译
编译线程
当达到编译阈值时,方法或循环就是进入编译队列,在后台异步地获取队列的代码进行编译。
编译队列不是严格的先进先出,执行次数越多的代码具有更高的优先级。这也是在上一章节,我们通过标志PrintCompilation查看被编译方法时,compilationg_id不完全按顺序递增的原因。
使用不同的编译器,在不同平台下会有不同的线程数,与平台的cpu数有关。
通常来说,使用client编译器,则会开启一个线程;使用server编译器,则会开启两个线程。当开启分层编译时,将会开启多个线程,在不同的平台的CPU数量下,线程数也会不同,在分层编译器中,会将client编译器称为C1编译器,将server编译器称为C2编译器,我们下满就这么称呼它们java代码编译,简单列举几个情况:
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
复制代码
默认是开启的,此参数对性能影响巨大,不建议关闭。
内联的条件
触发方法内联是有条件的,不是所有的代码都会被内联。
是否可以被内联取决于方法代码是否够热和它的大小。
逃逸分析
逃逸分析简单来说就是检查变量,查看变量的使用位置,检测其是否被其他范围所使用。如果它没有超出范围,那就是一个局部变量,我们能够针对这个变量做更多的优化。
如果被外部的方法调用,则称之为方法逃逸。 如果被外部的线程调用,则称之为线程逃逸。
在java8当中逃逸分析是默认被开启的:
[root@hecs-402944 opt]# jinfo -flag DoEscapeAnalysis 11210
-XX:+DoEscapeAnalysis
复制代码
开启逃逸分析后,server编译器将会进行很激进的优化:
当然还有很多复杂的优化,这里不过多介绍java代码编译,因为不建议我们针对逃逸分析做优化。
但是我们可以就逃逸分析解决一些问题:
原文链接: