类加载过程->Java代码是怎么编译运行的?

170 阅读5分钟

类加载过程

因为java是一门跨平台的语言,拥有一次编译到处运行的功能。

java源代码会编译为class文件,class文件是运行在JVM之上的

当我们日常开发安装JDK的时候,可以发现JDK是分不同的操作系统的,JDK里面是包含不同的JVM的,所以Java依赖着JVM实现了跨平台

JVM是面向操作系统的,它负责把Class字节码解释成系统所能识别的指令并执行,同时也负责程序运行时内存的管理。

image.png

JAVA源代码执行的过程有四个步骤:编译->加载->解释->执行

1.1 编译

编译是指将源文件(.java文件)编译成JVM虚拟机能够加载的class文件

编译过程会对程序源代码执行语法分析、语义分析、注解处理等等处理,最后生成字节码文件,

比如对泛型的类型擦除和我们经常使用的lombok就是在编译阶段干的11。

image.png

1.2 加载

加载指的是 将编译后的class文件加载到JVM中 在加载阶段可以细化为几个步骤: 装载->连接->初始化1

image.png

1.2.1 装载

  • 为了节省内存的开销,并不会一次性把所有的类都装载至JVM,而是等到「有需要」的时候才进行装载(比如new和反射等等)
  • class文件是通过「类加载器」装载到jvm中的,为了防止内存中出现多份同样的字节码,使用了双亲委派机制(它不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上)
  • JDK 中的本地方法类一般由根加载器(Bootstrp loader)装载,JDK 中内部实现的扩展类一般由扩展加载器(ExtClassLoader )实现装载,而程序中的类文件则由系统加载器(AppClassLoader )实现装载。

总结:这个阶段就是 查找并加载类的二进制数据,在JVM堆中创建一个java.lang.Class类的对象,并将类相关信息存储在JVM方法区

1.2.2 连接

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

  1. 验证:验证:验证类是否符合 Java 规范和 JVM 规范
  2. 准备:为类的静态变量分配内存,初始化为系统的初始值
  3. 解析:将符号引用转为直接引用的过程

总结:通过连接这个步骤,已经对class信息做校验并分配了内存空间和默认值。

1.2.3 初始化

为类的静态变量赋予正确的初始值。 过程大概就是收集class的静态变量、静态代码块、静态方法至()方法,随后从上往下开始执行。 如果「实例化对象」则会调用方法对实例变量进行初始化,并执行对应的构造方法内的代码。

image.png

1.3 解释

初始化完成之后,当我们尝试执行一个类的方法时,会找到对应方法的字节码的信息,然后解释器会把字节码信息编译成系统能够识别的指令码。

解释把字节码转换为操作系统能够识别的指令。 在解释阶段会有两种方式把字节码信息解释成机器码指令码,一个是字节码解释器,一个是即时编译器(JIT)。

image.png

JVM会对热点代码做编译,非热点代码直接进行解释。当JVM 发现某个方法或代码块的运行特别频繁的时候,就有可能把这部分代码认定为热点代码

使用热点检测来检测是否为热点代码。热点检测一般有两种方式,计数器和抽样。 HotSpot使用的是计数器的方式进行检测,为每个方法准备了两类计数器: 方法调用计数器和回边计数器。这两个计数器都有一个阈值,当计数器超过阈值溢出了,就会触发JIT编译。即时编译器把热点方法的指令码保存起来,下次执行的时候就无需重复地进行解释,直接执行缓存的机器语言。

1.4 执行

执行这个阶段它做的事情可以总结为:操作系统把解释器解析出来的指令码,调用系统的硬件执行最终的程序指令。

image.png

总结:

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