部分摘自https://blog.csdn.net/u010963948/article/details/90080056
部分摘自https://blog.csdn.net/qq_42764468/article/details/103690188
在Java中,JVM可以理解的代码就叫做字节码(即扩展名为.class文件),它不面向任何特定的处理器,只面向虚拟机。Java语言通过字节码的方式,在一定程度上解决了传统解释性语言执行效率低的问题,同时又保留了解释性语言可移植的特点。所以Java程序运行时比较搞笑,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译即可在多种不同的操作系统的计算机上运行。
Clojure(Lisp语言的一种方言)、Groovy、Scala等语言都是运行在Java虚拟机上。下图展示了不同的语言被不同的编译器编译成.class文件最终运行在Java虚拟机上,,class文件的二进制格式可以使用WinHex查看。
下面这个简单的Java代码,编译后生成Hello.class文件
package com.work.bytetest; public class Hello { public static void main(String[] args) { System.out.println("hello"); } }
我们使用WinHex打开这个文件可以看到字节码的详细内容
二、Class文件结构总结
根据Java虚拟机规范,类文件由单个ClassFile结构组成:
ClassFile { u4 magic; //Class 文件的标志 u2 minor_version;//Class 的小版本号 u2 major_version;//Class 的大版本号 u2 constant_pool_count;//常量池的数量 cp_info constant_pool[constant_pool_count-1];//常量池 u2 access_flags;//Class 的访问标记 u2 this_class;//当前类 u2 super_class;//父类 u2 interfaces_count;//接口 u2 interfaces[interfaces_count];//一个类可以实现多个接口 u2 fields_count;//Class 文件的字段属性 field_info fields[fields_count];//一个类会可以有个字段 u2 methods_count;//Class 文件的方法数量 method_info methods[methods_count];//一个类可以有个多个方法 u2 attributes_count;//此类的属性表中的属性数 attribute_info attributes[attributes_count];//属性表集合 }
下面介绍一下Class文件结构涉及到的一些组件。
Class文件字节码结构示意图(原出处不知道)
2.1魔数
每个class文件的头四个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的Class文件。
程序设计者很多时候都喜欢用一些特殊的数字表示固定的文件类型或者其他特殊的含义。
我们上面在WinHex中看到Hello.class文件的前四个字节是CA FE BA BE,每个类文件的前四个字节都是这个。
2.2Class文件版本
紧接着魔数的四个字节存储的是Class文件的版本号,第五和第六是次版本号,第七和第八是主版本号。
我们上面看到的次版本号是00 00,主版本号是00 34,16进制转成10进制应该就是52。
接下来就是常量池的数量:00 22,转成10进制就是34
一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看做是class文件的资源仓库。比如说:Java类中定义的方法与变量信息,都是存储在常量池中。
常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,java中声明为final的常量值等,而符号应用如类和接口的全局限定名,字段的名称和描述符。
常量池数组中的元素的个数=常量池数-1(其中0暂时不使用),我们这里就是33个,目的是满足某些常量池索引值的数据在特定情况下需要表达【不引用任何一个常量池】的含义;根本原因在于索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应于null值。
常量池数组紧跟在常量池数量之后,常量池数组与一般的数组不同的是,常量池数组中不同元素的类型,结构都是不同的,长度当然也不同;但是每一种元素的第一个数据都是一个u1类型,改自己是标志位,占据一个字节。jvm在解析常量池时,会根据这个u1类型来获取元素的具体类型。
下面根据使用WinHex软件显示的Hello.class在计算机磁盘中保存的16进制数据,反推Hello.class的内容:
第一个常量
0A是10,
第二个常量:
第三个常量:
第四个常量:
第五个常量:
第六个常量:
第七个常量为:
00 06转为10进制是6,那么后面6个字节是对应的值:
第八个常量:
00 03转为10进制就是3,表示接下来3个字节是对应的值:
第九个常量:
00 04转成10进制就是4,表示接下来4个字节是对应的值:
第十个常量:
对应的值:
第十一个常量:
对应的值:
第十二个常量:
对应的值:
第十三个常量:
对应的值:
这里19转为10进制是25
第十四个常量:
对应的值为:
第十五个常量:
对应的值为:
第十六个常量:
对应的值为:
第十七个常量:
对应的值为:
第十八个常量为:
对应的值为:
第十九个常量为:
对应值为:
第二十个常量为:
0C转为10进制为12
对应值:
第二十一个常量为:
第二十二个常量为:
对应的值为:
第二十三常量为:
对应的值为:
第二十四常量为:
对应的值为:
第二十五常量为:
对应的值为:
第二十六常量为:
对应的值为:
第二十七常量为:
对应的值为:
第二十八个常量值为:
对应的值为:
第二十九个常量值为:
对应的值为:
第三十个常量值为:
对应的值为:
第三十一个常量值为:
对应的值为:
第三十二个常量值为:
对应的值为:
第三十三个常量:
对应的值为:
接下来我们看到
access_flags为 00 21
this_class为00 05
super_class为00 06
interfaces_count为00 00
通过执行javap -verbose Hello.class可以得到如下信息
Classfile /C:/JavaBase/out/production/HashMapTest/com/work/bytetest/Hello.class Last modified 2020-12-16; size 548 bytes MD5 checksum b57a9425d15445dfb2a9a2dc0eb5fabe Compiled from "Hello.java" public class com.work.bytetest.Hello minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // hello #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // com/work/bytetest/Hello #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/work/bytetest/Hello; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 Hello.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 hello #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 com/work/bytetest/Hello #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V { public com.work.bytetest.Hello(); 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/work/bytetest/Hello; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "Hello.java"