`

Java虚拟机的平台无关性

阅读更多
我们知道,java 语言一次编写,到处运行,那它是如何做到的了?早在java语言设计之初,设计者就考虑到了这方面的事,他们将java规范拆分为java语言规范和java虚拟机规范,而虚拟机只和class文件进行绑定,所以不管是java语言,还是c语言,只要有编译器把*.java或*.c文件编译成*.class文件即可,然后class文件运行在 java虚拟机上就可以运行了.

那下面说下 class 的文件结构

任何一个class文件都对应唯一的一个类或接口,但是类或接口不一定要定义在 class 文件中. Class 文件是一组以 8 字节为基础单位的二进制流,各个数据项严格按照顺序紧密排列,中间没有任何分割符。如果要存储超过 8 字节的数据的话,那么就按照高字节在前,低字节在后进行存储。

Class 文件结构类似于 C 语言中的结构体,它只包含两种数据类型,无符号数和表,无符号数u1、u2、u4、u8 分别代表1字节、2字节、4字节和8字节,表是复合数据类型,由无符号数和表组成。所以从本质上来看,class 文件本质上是一张表。

无符号数多用于描述索引引用、数值、UTF-8编码的字符串,而表多以 "_info" 结尾,用于描述带有层级关系的数据。

有小伙伴会问如何存储数组类型了?

class 文件对于相同数据类型结构的存储,会在前面放一个长度计数器,后跟该数据类型(ps: 这个跟对象头很像哦)

下面正式介绍 class 文件格式.

使用 javap -verbose xxx.class

xhdeMacBook-Pro:fadp xh$ javap -verbose /Users/xh/workspace/jvm-read/target/classes/com/hanlin/fadp/StringGC.class
Classfile /Users/xh/workspace/jvm-read/target/classes/com/hanlin/fadp/StringGC.class
  Last modified 2020-2-5; size 720 bytes
  MD5 checksum 8c8a2d157d5cb8fe7ea8543bef59ed02
  Compiled from "StringGC.java"
public class com.hanlin.fadp.StringGC
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#27         // java/lang/Object."<init>":()V
   #2 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #30            // abcccds
   #4 = Methodref          #31.#32        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Long               10000l
   #7 = Methodref          #33.#34        // java/lang/Thread.sleep:(J)V
   #8 = Class              #35            // com/hanlin/fadp/StringGC
   #9 = Class              #36            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               Lcom/hanlin/fadp/StringGC;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               i
  #20 = Utf8               I
  #21 = Utf8               args
  #22 = Utf8               [Ljava/lang/String;
  #23 = Utf8               Exceptions
  #24 = Class              #37            // java/lang/InterruptedException
  #25 = Utf8               SourceFile
  #26 = Utf8               StringGC.java
  #27 = NameAndType        #10:#11        // "<init>":()V
  #28 = Class              #38            // java/lang/System
  #29 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
  #30 = Utf8               abcccds
  #31 = Class              #41            // java/io/PrintStream
  #32 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
  #33 = Class              #44            // java/lang/Thread
  #34 = NameAndType        #45:#46        // sleep:(J)V
  #35 = Utf8               com/hanlin/fadp/StringGC
  #36 = Utf8               java/lang/Object
  #37 = Utf8               java/lang/InterruptedException
  #38 = Utf8               java/lang/System
  #39 = Utf8               out
  #40 = Utf8               Ljava/io/PrintStream;
  #41 = Utf8               java/io/PrintStream
  #42 = Utf8               println
  #43 = Utf8               (Ljava/lang/String;)V
  #44 = Utf8               java/lang/Thread
  #45 = Utf8               sleep
  #46 = Utf8               (J)V
{
  public com.hanlin.fadp.StringGC();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/hanlin/fadp/StringGC;

  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: bipush        10
         5: if_icmpge     28
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: ldc           #3                  // String abcccds
        13: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: ldc2_w        #5                  // long 10000l
        19: invokestatic  #7                  // Method java/lang/Thread.sleep:(J)V
        22: iinc          1, 1
        25: goto          2
        28: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 16
        line 6: 22
        line 11: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2      26     1     i   I
            0      29     0  args   [Ljava/lang/String;
    Exceptions:
      throws java.lang.InterruptedException
}
SourceFile: "StringGC.java"

下面是用文本计数器打开 class 文件的截图:


从该文件中我们是不是没有发现魔数 CAFEBABE 这4字节了?但是我在 class 源文件中确实看到了。


先不管这个了,继续往下看是两个版本号,minor version 和 major version.

minor version 小版本(u2)
major version 大版本(u2)



接下来是常量池

常量池肯定存放多个元素,必然是 len(u2) + 元素 这种形式. 值得注意的是,常量池的下标是从 1 开始的.
002f -> 47,我们用 javap 查看到,一共 46 个元素,下标从 1 开始的正好核上了. 那空出来的 0 号位有啥用了?不是越紧促越好吗?设计者将 0 号位用于实现指向常量池数据但需要表达不指向任何一个常量池项目的含义.
常量池主要存放字面量和符号引用,字面量例如:文本字符串,被 fianl 修饰的常量,基本数据类型的值.
符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符.

常量池中每一项都是一个表,jdk1.7 之前共有 12 个结构.



0a -> 10  Constant_Methodref_info 类中方法的符号引用.
tag
index1 指向声明方法的类或接口信息 Constant_Class_info
index2 指向名称及类型描述符索引项 Constant_NameAndType_info

08 -> 8 Constant_String_Info 表示 String 类型的常量
tag
index 指向字符串字面量的索引

Constant_Utf8_info 表示 Utf8 编码的字符串(基石).
tag
length
bytes

Constant_Fieldref_info 类中的字段
tag
index1 指向声明该字段的类或接口描述符 Constant_Class_info
index2 指向字段描述符的索引项 Constant_NameAndType_info

Constant_Class_info
tag
index 指向全限定名称的索引

Constant_Integer_info、Constant_Double_info、Constant_Float_info、Constant_Long_info 都是 tag + bytes

Constant_NameAndType_info
tag
index 指向该字段名称或方法的索引项
index 指向该字段名称或方法描述的索引项

Constant_InterfaceMethod_info
tag
index 指向声明方法的接口描述符 Constant_Class_info 的索引项
index 指向名称及类型描述符 Constant_NameAndType_info 的索引项

以一个为例进行分析:

Constant_Methodref_Info 有两个索引项,一个执行声明该字段或方法的表,另一个指向该字段或方法的名称及类型描述表.
我们重点看下名称及类型描述表,根据名称我们就可以得出这个表必然有两个索引项,一个是指向名称,一个是指向描述。关于方法和字段的描述后面会详细讲。




jdk1.7 新增的表(后面统一说明)

Constant_MethodHandle_info 方法句柄
Constant_MethodType_info 方法的描述
Constant_InvokeDynamic_info

接着后面是访问标志

在常量池结束后,紧接着是两个字节的访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是
接口,是否定义为 public 类型,是否定义为 abstract 类型,如果是的话,是否被声明为 final 等.
access_flags 中一共有 16 个标志位可以使用,当前只定义了其中 8 个,没有使用到的标志位一律为 0.
例如:ACC_PUBLIC、ACC_SUPER、ACC_FINAL、ACC_INTERFACE 等.

类索引 & 父类索引 & 接口索引

类索引(this_class) 和父类索引(super_class) 都是一个 u2 类型的数据,而接口索引集合(interfaces) 是一组 u2 类型的数据的集合,
Class 文件中由这三项数据来确定这个类的继承关系. 类索引确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名. Java 是不允许
多重继承,所以父类索引只有一个. 接口索引集合用来描述这个类实现了那些接口.

类索引和父类索引指向一个类型为 Constant_Class_Info 的类描述符常量,通过 Constant_Class_Info 类型的常量中的索引值可以找到定义
在 Constant_Utf8_info 类型的常量中的全限定名字符串.

对于接口索引集合,入口的第一项是 u2 类型的数据为接口计数器(最大为 65535,所以在看 JDK Proxy 的时候,会发现它有限制接口数组的长度
为 65535 哦,原因就在这里).
1
1
分享到:
评论

相关推荐

    深入java虚拟机.pdf

    深入 Java 虚拟机.pdf ...Java 虚拟机提供了一个平台无关的环境,允许 Java 程序在不同的操作系统和硬件平台上运行。Java 虚拟机也提供了自动内存管理、垃圾收集、多线程支持等功能,提高了 Java 程序的可靠性和性能。

    java虚拟机规范高清中文版本(java SE 8版本)

    第1章 :简单地介绍了Java虚拟机的历史并吹捧了←_← 一下Java的平台无关性(一次编译,到处运行); 第2章:概览Java虚拟机整体架构; 第3章:介绍如何将Java语言编写的程序转换为虚拟机指令集; 第4章:定义...

    Java虚拟机规范 JavaSE7

    Java虚拟机(JVM)是Java程序运行的基础,它负责执行Java字节码,提供了一个与平台无关的执行环境。JVM规范定义了JVM的结构、指令集和运行时数据区,以及如何执行指令和处理异常。自1999年以来,JVM规范经历了多次...

    Java虚拟机(Java VM) msjavax86 微软java虚拟机

    这种跨平台能力得益于Java的平台无关性,Java虚拟机成为连接不同系统平台的关键桥梁。 微软Java虚拟机(msjavax86)是微软公司开发的一款JVM实现,专为Windows平台设计。与Sun Microsystems(现在被Oracle收购)的...

    Java虚拟机规范中文版.pdf

    综上所述,Java虚拟机规范是Java技术的核心,它为Java程序提供了一个平台无关的、安全的、可管理的运行环境。JVM规范的深入学习对于Java开发者来说至关重要,它不仅帮助开发者编写更好的Java程序,还能够深入理解...

    Java平台 Java虚拟机 Java 应用编程接口

    Java虚拟机允许Java程序在任何安装了Java平台的系统上运行,而无需针对特定操作系统进行重新编译。 Java平台由Java虚拟机和Java应用编程接口(API)组成。Java API是一套标准化的接口库,使得开发者可以编写与操作...

    深入Java虚拟机(原书第2版).pdf【附光盘内容】

    2.3 影响平台无关性的因素 2.3.1 java平台的部署 2.3.2 java平台的版本 2.3.3 本地方法 2.3.4 非标准运行时库 2.3.5 对虚拟机的依赖 2.3.6 对用户界面的依赖 2.3.7 java平台实现中的bug ...

    Java虚拟机在ARM—Linux平台的移植研究.pdf

    Java虚拟机是Java技术的重要组成部分,是程序与操作系统和硬件无关的关键。 在嵌入式Linux系统下,Java虚拟机的移植是实现Java程序在ARM平台上的运行环境的重要步骤。该研究的目的是实现Java虚拟机在ARM—Linux平台...

    Java 虚拟机面试题全面解析(干货)

    Java虚拟机的平台无关性是通过Java的Class文件实现的。Class文件由魔数、版本、常量池、类定义等部分组成,其结构是统一的,可以在任何安装了JVM的平台上运行。类的加载机制包括双亲委派模型,这是为了保证Java类...

    java虚拟机(微软版本) - java 虚拟机,微软版本,适合操作系统-windows.rar

    Java虚拟机是一种抽象的计算设备,它的设计目标是实现跨平台的代码执行。Java程序被编译成平台无关的字节码,然后由JVM负责解释执行。JVM通过Java类加载器加载类文件,执行字节码,并管理内存,包括堆和栈空间。此外...

    深入java虚拟机笔记

    综上所述,《深入Java虚拟机》这本书覆盖了Java体系结构、平台无关性、安全性、网络移动性以及JVM内部运作等多方面的内容,对于想要深入了解Java虚拟机及其工作机制的读者来说是非常有价值的参考资料。

    Java虚拟机规范(英文)

    "Java虚拟机"(Java Virtual Machine,简称JVM)是Java平台的运行环境,它通过解释字节码(bytecode)来实现Java程序的跨平台运行。这种设计使得Java程序能够在任何安装了相应JVM的机器上运行,而不必担心底层操作...

    Java运行原理与Java虚拟机.pdf

    字节码的设计确保了其与平台无关性,因为它们不包含任何特定硬件架构的信息。 2. **解释执行阶段**:字节码文件由Java虚拟机(JVM)解释执行。JVM是Java平台的核心组件,它负责将字节码转换为特定平台的机器码并执行...

    Java虚拟机运行机制

    Java虚拟机的规范对数据类型的内部格式进行了严格规定,使得各种Java虚拟机的实现对数据的解释是相同的,从而保证了Java的与平台无关性和可移植性。 Java虚拟机的实现需要深刻理解Java虚拟机的规范。Java虚拟机的...

    结合JAVA虚拟机的JAVA课程教学探索.pdf

    本文探索了JAVA虚拟机在JAVA课程教学中的应用,强调了JAVA虚拟机在JAVA技术体 系结构中的重要角色和地位,并讨论了如何结合JAVA虚拟机来解释JAVA的基本概念和运行机制,以帮助学生更好地理解JAVA的平台无关性、安全...

    Java 虚拟机规范.pdf

    由于提供的文件内容没有具体文字...Java 虚拟机规范是Java平台的重要组成部分,它不仅保证了Java语言的跨平台特性,也为Java的运行提供了强大的支撑。Java开发人员需要熟悉JVM规范,以便更好地编写和优化Java应用程序。

    Java虚拟机的深入研究

    JVM通过移植接口在不同平台上实现,移植接口包括适配器和Java操作系统,适配器部分根据目标平台进行定制,而JVM则保持一致,实现了Java的平台无关性。 JVM的体系结构包括类装载子系统和运行引擎。类装载子系统负责...

Global site tag (gtag.js) - Google Analytics