本文 首发于 🍀 永浩转载 请注明 来源

25、【对线面试官】java从编译到执行,发生了什么

从基础先问起吧,你是怎么理解Java是一门「跨平台」的语言,也就是「一次编译,到处运行的」?

  • 因为有JVM
  • Java源代码会被编译为class文件,class文件是运行在JVM之上的。
  • 当我们日常开发安装JDK的时候,可以发现JDK是分「不同的操作系统」,JDK里是包含JVM的,所以Java依赖着JVM实现了『跨平台』
  • 通俗点来讲,JVM是面向操作系统的,它负责把Class字节码解释成系统所能识别的指令并执行,同时也负责程序运行时内存的管理。

那要不你来聊聊从源码文件(java)到代码执行的过程呗?

  • 简单总结的话,我认为就4个步骤:编译->加载->解释->执行

    • 编译:将源码文件编译成JVM可以解释的class文件。

      • 编译过程会对源代码程序做「语法分析」「语义分析」「注解处理」等等处理,最后才生成字节码文件。
      • 比如对泛型的擦除和我们经常用的Lombok就是在编译阶段干的。
    • 加载:将编译后的class文件加载到JVM中。

      • 在加载阶段又可以细化几个步骤:装载->连接->初始化

        • 【装载时机】为了节省内存的开销,并不会一次性把所有的类都装载至JVM,而是等到「有需要」的时候才进行装载(比如new和反射等等)
        • 【装载发生】class文件是通过「类加载器」装载到jvm中的,为了防止内存中出现多份同样的字节码,使用了双亲委派机制(它不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上)
        • 【装载规则】JDK中的本地方法类一般由根加载器(Bootstrp loader)装载,JDK中内部实现的扩展类一般由扩展加载器(ExtClassLoader)实现装载,而程序中的类文件则由系统加载器(AppClassLoader)实现装载。
      • 装载这个阶段它做的事情总结:查找并加载类的二进制数据,在JVM「堆」中创建一个java.lang.Class类的对象,并将类相关的信息存储在JVM「方法区」中

        • 通过「装载」这个步骤后,现在已经把class文件装载到JVM中了,并创建出对应的Class.对象以及类信息存储至方法区了。
      • 「连接」这个阶段它做的事情总结:对class的信息进行验证、为「类变量」分配内存空间并对其赋默认值。

        • 连接又可以细化为几个步骤:验证-》准备-》解析

          1.验证:验证类是否符合Java规范和JVM规范

          2.准备:为类的静态变量分配内存,初始化为系统的初始值

          3.解析:将符号引用转为直接引用的过程

        • 通过「连接」这个步骤后,现在已经对class信息做校验并分配了内存空间和默认值了。

      • 「初始化」阶段总结:为类的静态变量赋予正确的初始值。

        • 过程大概就是收集class的静态变量、静态代码块、静态方法至clinit()方法,随后从上往下开始执行。
        • 如果「实例化对象」则会调用方法对实例变量进行初始化,并执行对应的构造方法内的代码。
    • 解释:把字节码转换为操作系统识别的指令

      • 在解释阶段会有两种方式把字节码信息解释成机器指令码,一个是字节码解释器、一个是即时编译器(JIT)
      • JVM会对「热点代码」做编译,非热点代码直接进行解释。当JVM发现某个方法或代码块的运行特别频繁的时候,就有可能把这部分代码认定为「热点代码」
      • 使用「热点探测」来检测是否为热点代码。「热点探测」一般有两种方式,计数器和抽样。HotSpot使用的是「计数器」的方式进行探测,为每个方法准备了两类计数器:方法调用计数器和回边计数器
      • 这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。
      • 即时编译器把热点方法的指令码保存起来,下次执行的时候就无需重复的进行解释,直接执行缓存的机器语言
    • 执行:操作系统把解释器解析出来的指令码,调用系统的硬件执行最终的程序指令。

总结

  • Java跨平台因为有JVM屏蔽了底层操作系统

  • Java源码到执行的过程,从JVM的角度看可以总结为四个步骤:编译->加载->解释->执行

    • 「编译」经过 语法分析、语义分析、注解处理 最后才生成会class文件
    • 「加载」又可以细分步骤为:装载->连接->初始化。装载则把class文件装载至JVM,连接则校验class信息、分配内存空间及赋默认值,初始化则为变量赋值为正确的初始值。连接里又可以细化为:验证、准备、解析
    • 「解释」则是把字节码转换成操作系统可识别的执行指令,在JVM中会有字节码解释器和即时编译器。在解释时会对代码进行分析,查看是否为「热点代码」,如果为「热点代码」则触发JIT编译,下次执行时就无需重复进行解释,提高解释速度
    • 「执行」调用系统的硬件执行最终的程序指令