JVM类加载

436 阅读8分钟

1、JVM类加载相关知识点

2、Java是解释型语言还是编译型语言?

  • 定义:
    • 编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。
    • 解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束!
  • 特点:
    • 编译型语言,执行速度快、效率高;依靠编译器、跨平台性差。
    • 解释型语言,执行速度慢、效率低;依靠解释器、跨平台性好。
  • 举例:
    • 编译型的语言包括:C、C++、Delphi、Pascal、Fortran
    • 解释型的语言包括:Java、Basic、javascript.
  • Java语言分两部分,首选源代码编译成Java字节码,然后通过JVM加载Java字节码逐条将字节码翻译成机器码并执行。
    • 首先:将Java文件编译成.class文件,.class文件就是java字节码,但Java字节码硬件视角来看,Java 字节码无法直接执行,所以可以说Java不是编译型语言。
    • 其次:JVM加载Java字节码,将Java字节码翻译成机器码的过程有两种形式
      • 第一种是解释执行,即逐条将字节码翻译成机器码并执行;
      • 第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。
  • HotSpot默认采用混合模式,前者的优势在于无需等待编译,而后者的优势在于实际运行速度更快。HotSpot默认采用混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。
  • JVM执行Java字节码的过程图
    JVM执行Java字节码的过程图

3、Java的即时编译(Just In Time, JIT)及其优化

Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块运行的特别频繁时,会把这些代码认定为“热点代码”(Hot Spot Code)。为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(JIT编译器,不是Java虚拟机内必须的部分)

解释器和编译器
  • 二者的优势:当程序需要快速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,越来越多的代码被编译成本地代码,可以获取更好的执行效率。解释器比较节约内存,编译器的效率比较高。解释器还可以作为编译器激进优化操作的“逃生门”,当激进优化的假设不成立,就退回到解释状态继续执行。
    解释器和编译器的交互
  • HotSpot内置了两个编译器,分别是Client Compiler和ServerComplier,或者简称为C1和C2编译器。同时用到两个编译器的分层编 译(TieredCompilation)策略,使用后,C1和C2同时工作,有些代码可能多次编译,用C1获取更高的编译速度,C2获取更好的编译质量:
    • 第0层,程序解释执行,解释器不开启性能监视功能(Profiling),可触发第1层编译。
    • 第1层,也称为C1编译,将字节码编译成本地代码,进行简单、可靠的优化,若有必要将加入性能监控的逻辑。
    • 第2层,也称为C2编译,也是将字节码编译成为本地代码,但是会启动一些编译耗时较长的优化,甚至会根据性能监控进行一些不可靠的激进优化。
热点代码
  • 热点代码定义

    • 被多次调用的方法
    • 被多次执行的循环体
  • 判断一段代码是不是热点代码,是不是需要触发即时编译,这样的行为称为热点探测(Hot Spot Detection),目前有两种方法:

    • 基于采样的热点探测:采用这样的方法的虚拟机会周期性的检查各个线程的栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。其好处就是实现简单、高效,还可以很容易的获取方法调用关系(将调用栈展开即可),缺点是很难精确的确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响。
    • 基于计数器的热点探测:为每一个方法(甚至是代码块)建立计数器,统计方法的执行次数,超过一定的阈值就认为是“热点方法”。缺点是实现起来更麻烦,需要为每个方法建立并维护计数器,并且不能直接获取到方法的调用关系,优点是它的统计结果相对来说更加精确和严谨。
  • HotSpot虚拟机使用第二种,它为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter,用于统计一个方法中循环体代码执行的次数)。

JVM 的静态绑定和动态绑定

Java 虚拟机识别方法的关键在于类名、方法名以及方法描述符(method descriptor)。前面两个就不做过多的解释了。至于方法描述符,它是由方法的参数类型以及返回类型所构成。在同一个类中,如果同时出现多个名字相同且描述符也相同的方法,那么 Java 虚拟机会在类的验证阶段报错。

  • 静态多分派:编译期间通过类的静态类型和调用参数确定。(重载)
  • 动态单分派:运行期间通过类的实际类型确定。(多态)

分配

4、Java三种编译方式

Java程序代码需要编译后才能在虚拟机中运行,编译涉及到非常多的知识层面:编译原理、语言规范、虚拟机规范、本地机器码优化等;了解编译过程有利于了解整个Java运行机制,不仅可以使得我们编写出更优秀的代码,而且还可以使得在JVM调优时更得心应手。下面我们先来看下Java体系中的三种编译方式:前端编译、即时编译(JIT编译)、静态提前编译(AOT编译),先来了解它们各有什么优点和缺点,再来看看主流的前端编译+JIT编译方式的运作过程。

  1. 前端编译:把Java源码文件(.java)编译成Class文件(.class)的过程;也即把满足Java语言规范的程序转化为满足JVM规范所要求格式的功能;
  • 优点:
    • 这阶段的优化是指程序编码方面的;
    • 许多Java语法新特性("语法糖":泛型、内部类等等),是靠前端编译器实现的,而不是依赖虚拟机;
    • 编译成的Class文件可以直接给JVM解释器解释执行,省去编译时间,加快启动速度;
  • 缺点:
    • 对代码运行效率几乎没有任何优化措施;
    • 解释执行效率较低,所以需要结合下面的JIT编译;
  • 前端编译器:Oracle javac、Eclipse JDT中的增量式编译器(ECJ)等;
  1. 后端编译/即时(JIT)编译:通过Java虚拟机内置的即时编译器(JIT编译器)在运行时把Class文件字节码编译成本地机器码的过程;
  • 优点:
    • 通过在运行时收集监控信息,把"热点代码"编译成与本地平台相关的机器码,并进行各种层次的优化;
    • 可以大大提高执行效率;
  • 缺点:
    • 收集监控信息影响程序运行;
    • 编译过程占用程序运行时间(如使得启动速度变慢);
    • 编译机器码占用内存;
  • JIT编译器:HotSpot虚拟机的C1、C2编译器等;
  • 另外:
    • JIT编译速度及编译结果的优劣,是衡量一个JVM性能的很重要指标;
    • 所以对程序运行性能优化集中到这个阶段;
    • 也就是说可以对这个阶段进行JVM调优;
  1. 静态提前编译(Ahead Of Time,AOT编译): 程序运行前,直接把Java源码文件(.java)编译成本地机器码的过程;
  • 优点:
    • 编译不占用运行时间,可以做一些较耗时的优化,并可加快程序启动;
    • 把编译的本地机器码保存磁盘,不占用内存,并可多次使用;
  • 缺点:
    • 因为Java语言的动态性(如反射)带来了额外的复杂性,影响了静态编译代码的质量;
    • 一般静态编译不如JIT编译的质量,这种方式用得比较少;
  • 静态提前编译器(AOT编译器):JAOTC、GCJ、Excelsior JET、ART (Android Runtime)等;