安卓面试必备的JVM虚拟机制详解,看完之后简历上多一个技能( 六 )


虚拟机相关先说 HotSpot 虚拟机 。
从硬件视角来看呢 , Java 字节码是无法直接运行的 , 因此 JVM 需要将字节码翻译成机器码 。 在 HotSpot 里面 , 翻译过程有两种 , 一种是解释执行 , 即逐条将字节码翻译成机器码并执行 , 第二种是即时编译执行 , 即以方法为单位整体编译为机器码后再执行 。 前者的优势在于无需等待编译 , 而后者的优势在于实际运行速度更快 。 HotSpot 默认采用混合模式 , 综合了解释执行和编译执行两者的优点 。 它会先解释执行字节码 , 而后将其中反复执行的热点代码 , 以方法为单位进行编译执行 。
HotSpot 内置了多个 JIT 即时编译器 , C1 和 C2 , 之所以引入多个即时编译器 , 是为了在编译时间和生成代码的执行效率之间进行取舍 。 Java 7 引入了分层编译 , 分层编译将 JVM 的执行状态分为 5 个层次 。 第 0 层是解释执行 , 默认开启性能监控;第 1 层到第 3 层都是称为 C1 编译 , 将字节码编译成本地代码 , 进行简单、可靠的优化;第 4 层是 C2 编译 , 也是将字节码编译成本地代码 , 但是会启用一些编译耗时较长的优化 , 甚至会根据性能监控信息进行一些不可靠的激进优化 。
至此 , HotSpot 及 JIT 就讲完了 。
再说 Dalvik 和 ART 。
HotSpot 是基于栈结构的 , 而 Dalvik 是基于寄存器结构 。 在官方文档上 , 已经没有 Dalvik 相关的信息了 , Android 5 后 , ART 全面取代了 Dalvik 。 Dalvik 使用 JIT 而 ART 使用 AOT 。 AOT 和 JIT 的不同之处在于 , JIT 是在运行时进行编译 , 是动态编译 , 并且每次运行程序的时候都需要对 odex 重新进行编译;而 AOT 是静态编译 , 应用在安装的时候会启动 dex2oat 过程把 dex 预编译成 oat 文件 , 每次运行程序的时候不用重新编译 。 另外 , 相比于 Dalvik , ART 对 GC 过程也进行了改进 , 只有一次 GC 暂停 , 而 Dalvik 需要两次 , 而且在 GC 保持暂停状态期间并行处理 。 AOT 解决了应用启动和运行速度问题的同时也带来了另外两个问题 , 一个是应用安装和系统升级之后的应用安装时间比较长 , 二是优化后的文件会占用额外的存储空间 。 在 Android 7 之后 , JIT 回归 , 形成了 AOT/JIT 混合编译模式 , 这种混合编译模式的特点是:应用在安装的时候 dex 不会被编译 , 应用在运行时 dex 文件先通过解释器执行 , 热点代码会被识别并被 JIT 编译后存储在 Code cache 中生成 profile 文件 , 再手机进入 IDLE(空闲)或者 Charging(充电)状态的时候 , 系统会扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译 。 这样一说 , 其实是和 HotSpot 有点内味 。
安卓面试必备的JVM虚拟机制详解,看完之后简历上多一个技能文章插图
面试问的关于JVM问题JVM 是如何执行方法调用的?其实呢就是了解 Java 编译器和 JVM 是如何区分方法的 。 方法重载在编译阶段就能确定下来 , 而方法重写则需要运行时才能确定 。
Java 编译器会根据所传入的参数的声明类型来选取重载方法 , 而 JVM 识别方法依赖于方法描述符 , 它是由方法的参数类型以及返回类型所构成 。 JVM 内置了五个与方法调用相关的指令 , 分别是 invokestatic 调用静态方法、invokespecial 调用私有实例方法、invokevirtual 调用非私有实例方法、invokeinterface 调用接口方法以及 invokedynamic 调用动态方法 。 对于 invokestatic 以及 invokespecial 而言 , JVM 能够直接识别具体的目标方法 , 而对于 invokevirtual 和 invokeinterface 而言 , 在绝大多数情况下 , JVM 需要在执行过程中 , 根据调用者的动态类型来确定具体的目标方法 。 唯一的例外在于 , 如果虚拟机能够确定目标方法有且只有一个 , 比如方法被 final 修饰 , 那么它就可以不通过动态类型 , 直接确定目标方法 。