继 上篇 classLoader加载完class文件后 jvm将执行该class类的静态方法或实例化对象进行调用。在源代码编译阶段将源代码编译为jvm字节码 jvmijiem是一种中间代码的形式。
jvm字节码要在jvm运行期间进行解释并执行,这种方式称为字节码解释执行方式。
由于采用的中间吗的方式,jvm有一套自己的指令,对于面向对象的语言而言 最重要的是执行方法的指令
jvm采取了invokestatic invokevirtual invokeinterface invokespecial 这四种指令来执行不同的方法调用。
invokestatic 对应的是调用static方法
invokevirtual 对应的是调用对象实例的方法
invokeinterface 对应的是调用接口的方法
invokespecial对应是调用private方法和编译源码后生产的<init>方法 此方法为对象实例化和初始化方法。
对于指令的执行方式分一下几步
1 指令解释执行
执行方式为经典的冯诺伊曼体系中的fdx循环方式,即获取下一条指令,解码并分派,然后执行,在实现fdx循环时有switch-threading token-threading direct-threading subroutine-threading inlin-threading 等多种方式。
2 栈顶缓存
在方法调用过程中可看到很多操作将要值放入操作数栈,这导致了寄存器和内存要不断的交换数据,jdk采用了一个栈顶缓存,既将本来要存入到数据栈的值直接缓存到寄存器上,这对于大部分只需要一个值的操作而言,无需将数据放入操作数栈,可以直接在寄存器上计算。然后放回操作数栈。
3 部分栈帧共享
当一个方法调用另外一个方法时,通常传入另一个方法的参数为已存放在操作数栈的数据,jdk在此做了一个优化,就是当调用方法时后一个方法可以将前一个方法的操作数栈作为当前方法的局部变量,从而节省数据cope带来的消耗。
对于jvm的解释执行补充,由于解释执行效率过低,为了提升代码的执行性能,jdk提供了 自己买变为机器码的支持
编译在运行时进行,通常称为jit编译器。jdk在执行过程中对执行频率交过的代码进行编译,对于执行不频繁的代码则继续采用解释的方式。因此jdk又称为hotspot vm
在编译sun jdk 提供了两种方式 client compiler server complier
client compiler 又称为 c1 较为轻量级 只做少量的性能开销比高的优化 它占用内存较少 适合于桌面上交互应用。在寄存器分配策略上 jdk6以后采用的线性扫描寄存器分配算法,在其他方面的优化主要有 方法内联、去虚拟化 冗余削除等。
1方法内联:
这个名词听起来怪怪的,其实简单来说就是当一个方法调用另外一个方法时,如果另外一个方法的字节小于35个字节 (启动参数中 -XX:MaxInlineSize=35 来控制的),该方法会将它调用的方法的代码包含进来。
2 去虚拟化
去虚拟化说的通俗一点就是不要玩虚的,比如我调用你的接口,你的接口就一个实现类,jvm在编译class时候就会直接调用你的这个实现类的具体实现方法,甚至可以将该实现方法内联到我的方法进来,进一步提升性能。
3 冗余削除
冗余削除是指在编译时 根据运行时状况进行代码折叠或削除。
这句话听起来很难理解,这样给你说吧,假如你代码里面有
private static final boolean flag=false;
public void execute(){
if(flag){
//do somthing
}
// do anther thing
}
经过代码的冗余削除,他机会剩下的代码结构如下
public void execute(){
// do anther thing
}
也就是它会精简掉你注定不会执行的代码
2 Server commpiler 又称为 C2 较为重量级的 C2 采用了大量的传统的编译优化技巧进行优化,占用内存会相对多一些,适合用于服务器端的应用。和c1不同的主要是在寄存器分配的策略上以及优化的范围,寄存器分配策略c2采用传统的图着色寄存器分配算法,由于c2会收集程序的运行信息,因此其优化的范围更多与全局的优化,而不仅仅是一个方法块的优化,收集的信息主要有 分支的跳转/不跳转的频率 某条指令上出现过的类型,是否出现过空值,是否出现过异常。
1 标量替换
标量替换的意思就是用标量替换聚合量
比如 Point point =new Point(1,2);
System.out.println("point.x="+point.x+"point.y="+point.y);
当point对象在后面的执行过程中未用到的话经过编译后代码类会变成下面结构
int x=1; int y=2; System.out.println(("point.x="+x+"point.y="+y);
这样做的好处是如果创建了对象,并未用到其中的全部变量,可以节省一丁点内存,对于代码执行而言,由于无需去对对象的引用,会更快一些。
2 栈上分配
对于上面的例子,对于point如果没有逃逸,则c2会选择在栈上直接创建point对象实例,而不是在jvm的堆上,在栈上分配的好处是一方面速度更快,另一方面回收时随着方法的结束,对象也会被回收,这就是栈上分配的概念。
3 同步削除
同步削除是指如果发现同步的对象未逃逸,那么就必有必要的同步了,在c2的编译时会直接去掉同步。
例如:
Point point =new Point(1,2);
synchronized(point){
//do somthing
}
经过分析后发现point未逃逸,在方法中引用了,则代码会变成以下结构
Point point =new Point(1,2);
// do somthing