zoukankan      html  css  js  c++  java
  • JVM 内部原理(五)— 基本概念之 Java 虚拟机官方规范文档,第 7 版

    JVM 内部原理(五)— 基本概念之 Java 虚拟机官方规范文档,第 7 版

    介绍

    版本:Java SE 7

    每位使用 Java 的程序员都知道 Java 字节码在 Java 运行时(JRE - Java Runtime Environment)里运行。Java 虚拟机(JVM - Java Virtual Machine)是 Java 运行时(JRE)的重要组成部分,它可以分析和执行 Java 字节码。Java 程序员不需要知道 JVM 是如何工作的。有很多应用程序和应用程序库都已开发完成,但是它们并不需要开发者对 JVM 有深入的理解。但是,如果你理解 JVM ,那么就可以对 Java 更有了解,这也使得那些看似简单而又难以解决的问题得以解决。

    在本篇文章中,我会解释 JVM 是如何工作的,它的结构如何,字节码是如何执行的及其执行顺序,与一些常见的错误及其解决方案,还有 Java 7 的新特性。

    目录

    • 虚拟机(Virtual Machine)

    • Java 字节码

      • 症状

      • 原因

    • 类文件格式(Class File Format)

      • 症状

      • 原因

    • JVM 结构

      • 类装载器(Class Loader)

      • 运行时数据区

      • 执行引擎

    • Java 虚拟机官方规范文档,第 7 版

      • 分支语句中的字符串
    • 总结

    内容

    Java 虚拟机官方规范文档,第 7 版

    在 2011 年 7 月 28 日,Oracle 发布了 Java SE 7 并更新了 JVM 官方规范文档至 Java SE 7 的版本。在 1999 年发布《Java 虚拟机官方规范文档,第二版》后,Oracle 花了 12 年时间做这版更新。更新版本的内容包括这 12 年来积累的各种变更修改,规范文档的描述更为清晰。除此之外,它还反映了《Java 语言规范文档,第七版》的内容。主要的更新概括如下:

    • Java SE 5.0 引入泛型,支持方法的参数变量。
    • 字节码验证过程的技术从 Java SE 6 开始发生变化。
    • 增加 invokedynamic 指令以及相关的类文件格式支持动态类型语言。
    • 删除了对于 Java 语言本身的概念性描述,并将其归入《Java 语言规范文档》中。
    • 删除了关于 Java 线程和锁的描述,并将其写入《Java 语言规范文档》。

    最大的改变要数增加 invokedynamic 指令。这也意味着 JVM 内部指令集发生了变化,也就是说 JVM 从 Java SE 7 开始支持类型非固定的动态类型语言,如脚本语言,以及动态的 Java 语言。之前没有使用的操作码(OpCode)186 被应用到新指令 invokedynamic 以及新的类文件格式中以支持动态性,

    由 Java 编译器 Java SE 7 创建的类文件版本是 51.0 。Java SE 6 的版本是 50.0 。类文件格式发生了很大变化,因此 51.0 版本的类文件不能运行于 Java SE 6 的 JVM 。

    尽管有如此之多的变化,Java 方法的 65535 字节长度限制并没有被移除。除非 JVM 类文件格式发生了创新式的变化,否则它也不太可能在将来移除。

    Oracle Java SE 7 VM 支持 G1 ,这个新的垃圾回收机制;不过,它仅限于 Oracle JVM ,所以 JVM 本身并不受限于任何垃圾回收机制。因此,JVM 官方规范文档并没有对此进行描述。

    分支语句中的字符串

    Java SE 7 增加了多种语法和特性。不过,与 Java SE 7 中语言发生的许多变化相比,JVM 并没有发生很多变化。那么,Java SE 7 的新特性是如何实现的呢?我们通过反编译看看 String 在分支语句中(一个将字符串传入 switch() 语句进行比较的功能)的实现方式。

    有如下代码:

    // SwitchTest
    public class SwitchTest {
        public int doSwitch(String str) {
            switch (str) {
            case "abc":        return 1;
            case "123":        return 2;
            default:         return 0;
            }
        }
    }
    

    因为它是 Java SE 7 的新功能,所以它不能使用 Java SE 6 或更低版本的编译器来编译。用 Java SE 7 的 javac 编译它。用 javap -c 显示编译结果:

    C:Test>javap -c SwitchTest.classCompiled from "SwitchTest.java"
    public class SwitchTest {
      public SwitchTest();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return  public int doSwitch(java.lang.String);
        Code:
           0: aload_1
           1: astore_2
           2: iconst_m1
           3: istore_3
           4: aload_2
           5: invokevirtual #2                  // Method java/lang/String.hashCode:()I
           8: lookupswitch  { // 2
                     48690: 50
                     96354: 36
                   default: 61
              }
          36: aload_2
          37: ldc           #3                  // String abc
          39: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
          42: ifeq          61
          45: iconst_0
          46: istore_3
          47: goto          61
          50: aload_2
          51: ldc           #5                  // String 123
          53: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
          56: ifeq          61
          59: iconst_1
          60: istore_3
          61: iload_3
          62: lookupswitch  { // 2
                         0: 88
                         1: 90
                   default: 92
              }
          88: iconst_1
          89: ireturn
          90: iconst_2
          91: ireturn
          92: iconst_0
          93: ireturn
    

    字节码的内容要比 Java 源代码多很多。首先,可以看到 lookupswitch 指令已经被 switch() 语句使用到字节码中。但是,有两个 lookupswitch 指令而不是一个。反编译后可以看到 int 传入 switch() 语句,但是只有一个 lookupswitch 指令用到了。也就是说 switch() 语句被拆分成了两个语句来处理字符串。查看 #5、#39 和 #53 号字节指令看 switch() 语句是如何处理字符串的。

    在 #5 和 #8 号字节中,首先,hashCode() 方法被执行,然后 switch(int) 是通过使用 hashCode() 方法的运行结果来执行的。在 lookupswitch 指令的括号中,分支根据 hashCode 的值来定位到不同地方。字符串 “abc” 的 hashCode 结果是 96354 ,被定位到 #36 号字节。字符串 “123” 的 hashCode 结果是 48690 ,被定位到 #50 号字节。

    在 #36、#37、#39 和 #42 号字节中,可以发现接收的 str 变量值作为参数与 String “abc” 和 equals() 方法比较。如果结果相同,“0” 被插入到 #3 号本地变量列表的索引位置,字符串被移动到 #61 号字节。

    通过这种方式,在 #50、#51、#53 和 #56 字节中,可以发现接收的 str 变量值作为参数与 String “123” 和 equals() 方法比较。如果结果相同,“1” 被插入到 #3 号本地变量列表的索引位置,字符串被移动到 #61 号字节。

    在 #61 和 #62 号字节中,以 #3 号本地变量列表的索引位置的值,如:“0”、“1” 或其他值,进行 lookupswitch 和分支处理。

    换句话说,在 Java 代码中,接收的 str 变量值作为 switch() 参数使用 hashCode() 方法和 equals() 方法进行比较。switch() 方法根据 int 值的结果执行。

    在这个结果中,被编译的字节码与之前的 JVM 规范文档并没有任何不同。Java SE 7 的新特性,字符串分支语句,是由 Java 编译器来处理的,而不是 JVM 本身。以类似的方式,Java SE 7 的其他新特性也是通过 Java 编译器进行处理的。

    总结

    这里评审 Java 语言是如何设计让 Java 可以更容易的使用没有太大必要。有很多 Java 程序员并没有对 JVM 有很深入的了解,却也开发出了很多优秀的应用和库。不过,如果能够深入理解 JVM ,我们就能处理这些例子中出现的问题。

    除了这里提到的内容,JVM 有很多特性和技术。JVM 规范文档为 JVM 厂商提供了灵活的空间,让他们应用各种不同的技术从而提供更好的性能。特别是垃圾回收技术,它被大多数语言使用,提供与 VM 类似的使用方式以及最新最前沿的性能优化技术。但是,这些内容在很多卓越的研究中都会被讨论到,在此不作深入解释。

    参考

    参考来源:

    JVM Specification SE 7 - Run-Time Data Areas

    2011.01 Java Bytecode Fundamentals

    2012.02 Understanding JVM Internals

    2013.04 JVM Run-Time Data Areas

    Chapter 5 of Inside the Java Virtual Machine

    2012.10 Understanding JVM Internals, from Basic Structure to Java SE 7 Features

    2016.05 深入理解java虚拟机

    结束

  • 相关阅读:
    Minecraft 1.12.2/1.14.4 Mod开发笔记——搭建环境
    Minecraft 1.12.2 Mod开发笔记
    浅谈莫比乌斯反演
    卡迈克尔数
    一些可能会有用的东西(持续更新)
    emacs配置
    CSPS 2020游记
    浅谈KMP
    Atcoder AGC052
    乌班图操作指令(持续更新)
  • 原文地址:https://www.cnblogs.com/richaaaard/p/6196361.html
Copyright © 2011-2022 走看看