美文网首页
Java代码的运行

Java代码的运行

作者: 纸箱子的一 | 来源:发表于2020-11-12 20:38 被阅读0次

为什么Java代码需要在虚拟机中运行

  • Java 代码作为一种高级语言直接在硬件中执行是不现实的,所以在代码运行前需要进行转换。Java虚拟机通过编译器将Java代码进行转换为虚拟机可识别的指令序列(Java字节码)。
  • 虚拟机可以由硬件来实现,但通常是由软件来实现。由软件实现的有各种平台的虚拟机(Windows,Mac等),由虚拟机转换后的Java字节码文件,即可在不同平台实现的虚拟机中运行。
  • 虚拟机还带来的另一个好处就是有一个托管环境,使程序员更主要关心业务代码。例如自动内存管理和垃圾回收。

Java虚拟机怎样运行字节码

分两个视角:

  • 虚拟机视角:执行Java代码需要先编译成class文件,然后将其加载到虚拟机中。加载后的Java类会放在方法区中,实际运行时,虚拟机会执行方法区中的代码。
    在运行过程中,每当调用进入一个方法,虚拟机就会在当前线程的方法栈中生成一个栈桢,用来存放局部变量和字节码的操作数。这个栈桢是提前计算好的,并且在内存中不需要连续。
  • 硬件视角:Java字节码是无法直接运行的,需要虚拟机将字节码翻译为机器可识别的机器码。这个翻译的过程有两种形式:解释执行:逐条将字节码翻译为机器码并执行 和 即时编译(JIT):将一个方法中包含的所有字节码翻译为机器码后再执行。
    • 前者的优势在于无需等待编译,后者的优势在于实际运行速度更快。HotSpot采用混合模式,先解释执行字节码,然后将需要反复执行的热点代码,进行以方法为单位进行即时编译。

Java虚拟机的运行效率

根据二八定律,其实大部分的代码是不需要提前编译的,是可以直接解释执行的。仅需将少部分的热点代码进行编译,达到提升效率。
为了满足不同客户的需求,HotSpot内置了多个即时编译器:C1、C2 和 Graal。在编译时间和生成代码的执行效率之间进行取舍。

  • C1编译器又叫Client编译器,主要面向对启动时间有要求的GUI用户。
  • C2编译器又叫Server编译器,主要面向对峰值性能有要求的服务端。编译时间长但是生成的代码执行效率高。
  • Java7以后采用分层编译,优先被C1编译,而后被C2编译,并根据CPU的数量设置编程线程的数量,按照1:2比例分配给C1,C2。

再回顾一下Java内存区域划分

Java虚拟机运行时将内存划分为5个部分:方法区、堆、PC寄存器、Java方法栈和本地方法栈

对于热点代码的判断可看链接

JVM会统计每个方法被调用了多少次,超过多少次,那就是热点方法。(还有个循环回边计数器,用来编译热循环的。) 默认的分层编译应该是达到两千调C1,达到一万五调C2。
https://mp.weixin.qq.com/s/GO2fAeGgaB2jIC02gWp5Aw
分享一个工具 asmtools 可以修改字节码指令文件
https://ci.adoptopenjdk.net/view/Dependencies/job/asmtools/lastSuccessfulBuild/

public class Foo {
    public static void main(String[] args) {
        boolean flag = true;
        if(flag){
            System.out.println("Hello,Java");
        }
        if(flag==true){
            System.out.println("Hello,JVM");
        }
    }
}
super public class Foo
        version 52:0
{
  public Method "<init>":"()V"
        stack 1 locals 1
  {
                aload_0;
                invokespecial   Method java/lang/Object."<init>":"()V";
                return;
  }
  public static Method main:"([Ljava/lang/String;)V"
        stack 2 locals 2
  {
                iconst_1;  //@1 用asmtools 工具将此处修改为 iconst_2 那么程序输出结果为 Hello,Java。
                istore_1;
                iload_1;
                ifeq    L14;
                getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
                ldc     String "Hello,Java";
                invokevirtual   Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
        L14:    stack_frame_type append;
                locals_map int;
                iload_1;
                iconst_1;
                if_icmpne       L27;
                getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
                ldc     String "Hello,JVM";
                invokevirtual   Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
        L27:    stack_frame_type same;
                return;
  }

} // end Class Foo

jvm把boolean当做int来处理
flag = iconst_1 = true
修改后flag改为iconst_2
if(flag)比较时ifeq指令做是否为零判断,常数2仍为true,打印输出
if(true == flag)比较时if_cmpne做整数比较,iconst_1是否等于flag,比较失败,不再打印输出。
-- 这个来自极客时间中的评论。

相关文章

网友评论

      本文标题:Java代码的运行

      本文链接:https://www.haomeiwen.com/subject/jcppbktx.html