java类得执行机制分为字节码解释执行和编译为机器码执行,后者又分为client compiler 和server compiler
1、字节码解释执行
JVM是一种中间代码的方式 ,在执行时候,JVM有自己的一套指令,JVM采用invokestatic、invokevirtual、invokeinterface、invokespecial来查找执行不同的方法。
invokestatic会调用static方法,invokevirtual会调用对象实例的方法,invokeinterface会调用接口的方法,invokespecial会调用私有的方法和编译之后<init>方法,此方法为对象实例化的初始化方法,如下面的代码:
public class Demo{ public void execute{ A.execute();//调用invokestatic A a =new A();//调用invokespecial a.am();//调用invokevirtual IB b = new B();//调用invokespecial b.bm();//调用invokeinterface } } public class A{ public static String execute(){ return "A"; } public am(){ return "am"; } } public interface IB{ public String mb(); } public class B implements IB{ public String mb{ return "mb"; } }
以上是四种指令对应方法的情况。
2、编译执行
1)client compiler(C1)
client compiler又称为C1,只做少量性能开销比较高的优化,占内存比较小,适合桌面应用程序,其中采用的优化方式主要有方法内联、冗余消除、去虚拟化等。
①方法内联
通常一个业务逻辑方法,要调用多个方法来完成,所谓的方法内联就是把方法体内的代码直接植入到当前的方法体内,如下面代码:
public void m1(){ //..... m2(); //... } private void m2(){ //do..... }当编译的时候 ,m2()字节数小到规定范围内时候,就会将m2()的方法体直接植入到m1()内,如下:
public void m1(){ //..... //do..... //... }
②去虚拟化
主要是对类层次进行分析,当一个借口只有一个实现类的时候,那么在调用方法的时候,会将方法植入到方法体内,如:
piblic interface IParent(){ public void m(); } public Child implements Iparent{ public void m(){ //do..... } } public class Demo{ public void ip(Iparent child){ child.m(); } }由于只有Child实现IParent,最后编译的代码为:
public void ip(Iparent child){ //do.... }
主要精简代码和消除无用的代码
如:
boolean status = false; public void m(){ if(status){ //do.... }
//do..something....... }编译之后为:
public void m(){ //do...something.... }
C2占用的内存比较多,但是用很多的优化技巧,它是基于逃逸分析进行优化的,所谓逃逸分析,就是一个方法内的一个变量 ,如果没有被外部引用过,那么就认为该变是逃逸的,否则是没有逃逸,基于逃逸分析的C2会做标量替换、栈上分配、同步消除等。
①标量替换
就是变量替换聚合量,如:
B b = new B("Tom mao"); System.out.println(b.name);
如果b在之后没有被用到过,那么标量替换后的代码为:
String name = "Tom mao"; System.out.println(name);②栈上分配
就是把局部变量直接分配在栈上,当方法结束后 ,标量占用的内存也会随之而释放。
③同步消除
就是如果发现同步的对象 没有逃逸,那么就没有同步的必要,就会去掉同步的代码。
默认情况下 SUN JDK会根据机器的配置来选择相应的编辑为机器码方式,当机器配置cpu超过2核且内存超过2G会采用C2 ,但是在windows机器上始终会采用C1,可以启动时候加上-client或者-server来强制指定。