JVM——深入理解类执行机制

文章详细介绍了JVM如何执行字节码,包括解释执行的过程,如Invokestatic、Invokevirtual等指令的作用,以及栈帧和栈顶缓存的概念。同时,阐述了SunJDK的两种编译器C1和C2的工作原理,如方法内联、去虚拟化、冗余削除等优化手段,以及C2中的逃逸分析和相关优化策略。
摘要由CSDN通过智能技术生成

在把class文件加载到JVM中且产生了Class对象后,就可以执行Class对象的静态方法和实例化调用了,源码编译阶段源码被编译成JVM字节码,JVM字节码是作为一种中间码的方式,在运行期需要对它进行解释并执行,这种方式称作字节码解释执行方式。

字节码的解释执行

JVM自己有一套对JVM字节码中的方法的执行指令

Invokestatic:这个指令用于执行类的静态方法

Invokevirtual:这个指令用于执行调用类的实例方法

Invokeinterface:这个指令用于执行调用类中继承自接口的方法

Invokespecial:这个指令用于执行类的private修饰的方法,以及编译源码后生成的<init>方法,此方法为对象实例化时的初始化方法

SUN JDK通过采用栈的体系执行字节码,线程在创建后都会产生程序计数器(PC)和栈,PC存放了下一条要执行的指令在方法内的偏移量。栈中存放了栈帧,每个方法的调用都会产生栈帧,栈帧主要包括两部分,分别是局部变量区和操作数栈,局部变量区包含着方法的参数和局部变量,操作数栈主要存放方法执行过程中产生的中间数,有时栈帧中还会存在一些其他空间,例如指向方法已解析的常量池的引用。

指令解释执行

对于方法的指令的解释执行,遵照冯诺依曼的FDX循环方式,即获取下一条指令,解释并分派,然后执行,后面也做了大量的改进

栈顶缓存

在方法的执行过程中,有很多值都需要放到操作数栈中,这就导致了内存和寄存器(寄存器在CPU内部)要不断的交换数据,Sun JDK采用了栈顶缓存的方式,即将本来位于操作数栈顶的值直接缓存在寄存器中,这样就可以直接在寄存器中计算,然后放回操作数栈

部分栈帧共享

当一个方法调用另一个方法时,通常传入另一方法的参数为操作数栈的数据,就会产生大量的copy操作,Sun JDK做了一个优化,当调用方法时,后一个方法可将前一个方法的操作数栈作为当前方法的局部变量,从而节省copy的消耗

 编译执行

 解释执行的效率较低,为了提升代码的执行性能,Sun JDK提供将字节码编译为机器码的支持,这个编译在运行时进行,通常称为JIT编译器。Sun JDK对执行效率高的代码进行编译,对执行不频繁的代码进行解释执行的方式。

关于编译Sun JDK提供了两种模式:client compilerserver compiler

client compiler (C1)

client compiler又称为C1,较为轻量级,只做少量性能开销比较高的优化,适合于桌面化应用。C1的主要优化有:方法内联、去虚拟化、冗余削除。

方法内联

对于面向对象的语言,通常要调用多个方法来完成功能。执行时要经历多次方法的传递以及跳转等,于是C1采用了方法内联的方式,把要调到的方法的指令植入到当前方法中。

举个例子

public void bar(){
bar2();
private void bar2(){
//bar2
}
方法内联后:
public void bar(){
//bar2
}

去虚拟化

去虚拟化是指在装载class文件后,进行类层次的分析,如果发现类或接口的方法只有一个类实现了该方法,那么对于调用此方法的代码,也可以进行方法内联,例如:

public interface IFoo{
public void bar();
}
public class Foo implements IFoo{
public void bar(){
//Foo bar method
}
public class Demo{
public void execute(IFoo foo){
foo.bar();
)
}
//方法内联后:(当JVM中只有Foo实现了IFoo接口后,Demo的execute方法被编译时)
public void execute(){
//Foo bar method
}

冗余削除

冗余削除是指在编译时,根据运行时状况进行代码折叠或削除。

例如一些if(m){....}的代码返回m为false就会把if语句直接折叠或削除,增加运行时速度

server compiler (C2)

server compiler又称为C2,较为重量级,C2采用了大量编译优化技巧进行优化,占用的内存会更多一点,适合于服务器端的应用。和C1不同的主要是分配策略以及优化范围,由于C2会收集程序的运行信息,因此其优化的范围更多在于全局的优化,而不仅仅是一个方法块的优化,收集的信息主要有:分支的跳转/不跳转,某条指令出现过的类型、是否出现过空值、是否出现过异常。

逃逸分析

逃逸分析是C2进行许多操作的基础,逃逸分析是根据运行状况来判断方法中的变量是否会被外部读取。如过不会则认为此变量是逃逸的。基于逃逸分析C2在编译时会做标量替换、栈上分配和同步削除等优化。

标量替换

标量替换的意思简单来说就是用标量替换聚合量

标量:在java中基础类型不可再分属于标量,同样引用也属于标量,而对象本身则是聚合量。

例子:

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);

这样做的好处是,如果创建的对象并未用到全部变量,则可以节省一定的内存。

栈上分配

在上面的例子中,如果point没有逃逸,那么C2会选择在栈上直接创建Point对象实例,而不是在JVM堆上。在栈上分配的好处就是更加快速,另一方面是回收时随着方法结束了,对象也被回收了。

同步削除

同步削除是指如果发现同步的对象未逃逸,那也没有同步的必要了,在C2中会直接去掉同步

例子:

Point point=new Point (1,2);
synchronized(point){
//do something
}
//如果发现point未逃逸
Point point=new Point (1,2);
//do something

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值