我们知道Java是一门跨平台的语言,我们编写的Java代码会被编译成中间class文件以让Java虚拟机解析运行。而Java虚拟机规范仅仅描述了抽象的Java虚拟机,在实现具体的Java虚拟机时,仅指出了设计规范。Java虚拟机的实现必须体现规范中的内容,但仅在确有必要时才应该受制于这些规范。对于完整内容,可以查看原文档,以JDK7为例,可查看 https://docs.oracle.com/javase/specs/jvms/se7/html/ ,或者《深入理解Java虚拟机 JVM高级特性与最佳实践》一书。完整的规范主要包含以下内容:
本文只是大概记录项目需要了解的基础概念,着重在介绍Class文件格式上,为该系列后续内容做铺垫。
Class文件是一组以8字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在class文件中,中间没有任何分割符。每个 Class 文件都是由 8 字节为单位的字节流组成,所有的 16 位、32 位和 64 位长度的数据将被构造成 2 个、4 个和 8 个 8 字节单位来表示。
每一个Class文件对应于一个如下所示的ClassFile结构体:
涉及到的内容包括:
可用jdk自带的javap命令对class文件进行反编译,以查看内容,如下代码:
public class Ex { public void judgeAge(int age) { int step = 0; if (age > 18) { step++; System.out.println("a litter old"); } else { System.out.println("a litter cute"); step++; } System.out.println(step); } public static void main(String[] args) { Ex ex = new Ex(); ex.judgeAge(16); } }
执行 javap -verbose -p Ex.class的结果为
Classfile Ex.class Last modified 2019-11-29; size 788 bytes MD5 checksum 8b5d8ebf38c4441fe7150c10da31ce1b Compiled from "Ex.java" public class Ex minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#31 // java/lang/Object."<init>":()V #2 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #34 // a litter old #4 = Methodref #35.#36 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = String #37 // a litter cute #6 = Methodref #35.#38 // java/io/PrintStream.println:(I)V #7 = Class #39 // Ex #8 = Methodref #7.#31 // Ex."<init>":()V #9 = Methodref #7.#40 // Ex.judgeAge:(I)V #10 = Class #41 // java/lang/Object #11 = Utf8 <init> #12 = Utf8 ()V #13 = Utf8 Code #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 this #17 = Utf8 LEx; #18 = Utf8 judgeAge #19 = Utf8 (I)V #20 = Utf8 age #21 = Utf8 I #22 = Utf8 step #23 = Utf8 StackMapTable #24 = Utf8 main #25 = Utf8 ([Ljava/lang/String;)V #26 = Utf8 args #27 = Utf8 [Ljava/lang/String; #28 = Utf8 ex #29 = Utf8 SourceFile #30 = Utf8 Ex.java #31 = NameAndType #11:#12 // "<init>":()V #32 = Class #42 // java/lang/System #33 = NameAndType #43:#44 // out:Ljava/io/PrintStream; #34 = Utf8 a litter old #35 = Class #45 // java/io/PrintStream #36 = NameAndType #46:#47 // println:(Ljava/lang/String;)V #37 = Utf8 a litter cute #38 = NameAndType #46:#19 // println:(I)V #39 = Utf8 Ex #40 = NameAndType #18:#19 // judgeAge:(I)V #41 = Utf8 java/lang/Object #42 = Utf8 java/lang/System #43 = Utf8 out #44 = Utf8 Ljava/io/PrintStream; #45 = Utf8 java/io/PrintStream #46 = Utf8 println #47 = Utf8 (Ljava/lang/String;)V { public Ex(); 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 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LEx; public void judgeAge(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=2 0: iconst_0 1: istore_2 2: iload_1 3: bipush 18 5: if_icmple 22 8: iinc 2, 1 11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 14: ldc #3 // String a litter old 16: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 19: goto 33 22: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 25: ldc #5 // String a litter cute 27: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: iinc 2, 1 33: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 36: iload_2 37: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 40: return LineNumberTable: line 4: 0 line 5: 2 line 6: 8 line 7: 11 line 9: 22 line 10: 30 line 12: 33 line 13: 40 LocalVariableTable: Start Length Slot Name Signature 0 41 0 this LEx; 0 41 1 age I 2 39 2 step I StackMapTable: number_of_entries = 2 frame_type = 252 /* append */ offset_delta = 22 locals = [ int ] frame_type = 10 /* same */ public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #7 // class Ex 3: dup 4: invokespecial #8 // Method "<init>":()V 7: astore_1 8: aload_1 9: bipush 16 11: invokevirtual #9 // Method judgeAge:(I)V 14: return LineNumberTable: line 16: 0 line 17: 8 line 18: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 args [Ljava/lang/String; 8 7 1 ex LEx; } SourceFile: "Ex.java"
下面对后续需要接触到的几项内容做说明。
Class文件中有两种数据类型,分别是无符号数和表:
带有 ACC_SYNTHETIC 标志的部分,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。有该标志的类、属性、方法等不会在源码中显示。
基本类型的描述是单个字符:
方法描述符是一个类型描述符列表,它用一个字符串描述一个方法的参数类型和返回类型。
方法描述符以左括号开头,然后是每个形参的类型描述,然后是一个右括号,接下来是返回类型的类型描述符,如果该方法返回void,则是 V,表示方法描述中不包含方法的名字或参数名,可看如下例子:
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
常见的指令如下:
PS:笔者个人习惯使用Bytecode Outline进行反编译,这款插件输出的内容可读性会高点,上面的内容输出下:
// class version 52.0 (52) // access flags 0x21 public class Ex { // compiled from: Ex.java // access flags 0x1 public <init>()V L0 LINENUMBER 1 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this LEx; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x1 public judgeAge(I)V L0 LINENUMBER 4 L0 ICONST_0 ISTORE 2 L1 LINENUMBER 5 L1 ILOAD 1 BIPUSH 18 IF_ICMPLE L2 L3 LINENUMBER 6 L3 IINC 2 1 L4 LINENUMBER 7 L4 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "a litter old" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V GOTO L5 L2 LINENUMBER 9 L2 FRAME APPEND [I] GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "a litter cute" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V L6 LINENUMBER 10 L6 IINC 2 1 L5 LINENUMBER 12 L5 FRAME SAME GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ILOAD 2 INVOKEVIRTUAL java/io/PrintStream.println (I)V L7 LINENUMBER 13 L7 RETURN L8 LOCALVARIABLE this LEx; L0 L8 0 LOCALVARIABLE age I L0 L8 1 LOCALVARIABLE step I L1 L8 2 MAXSTACK = 2 MAXLOCALS = 3 // access flags 0x9 public static main([Ljava/lang/String;)V L0 LINENUMBER 16 L0 NEW Ex DUP INVOKESPECIAL Ex.<init> ()V ASTORE 1 L1 LINENUMBER 17 L1 ALOAD 1 BIPUSH 16 INVOKEVIRTUAL Ex.judgeAge (I)V L2 LINENUMBER 18 L2 RETURN L3 LOCALVARIABLE args [Ljava/lang/String; L0 L3 0 LOCALVARIABLE ex LEx; L1 L3 1 MAXSTACK = 2 MAXLOCALS = 2 }
更多原创内容请搜索微信公众号:啊驼(doubaotaizi)