目录

咕咕咕~

    无论怎样的语言,它们编写的程序到最后一定要经过编译器编译为机器码才能被计算机执行。因为计算机只认识0和1,一般情况下编写的程序要想在别的平台上运行需要经过重新编译,而Java从一开始就有一个口号:

Write Once, Run Anywhere.

    为了实现这个目的,Sun(Oracle)以及其他的厂商发行了许多在不同平台上运行的JVM虚拟机,而这些虚拟机都有一个共同的功能,那就是可以载入和当前平台无关的字节码(ByteCode)。于是程序的源代码再也不用在不同平台上重新编译,而是直接编译为字节码,然后把字节码交给不同平台的不同JVM去执行,从而实现一次编写到处运行的目的。JVM不仅仅只支持Java,还派生了许多基于JVM的编程语言,例如jetbrains的kotlin以及scala等等。

class


关于字节码

    用javac编译一个.java文件会生成一个.class文件,如果以16进制的方式打开这个文件会看到:

16hex

    噩梦,一堆十六进制的代码。而我们只认识开头的cafe babe,这是Java 类文件的Magic Number只有以cafe babe开头的class文件才会被JVM接受。

关于cafe babe的由来有着一段有趣的故事:

    据詹姆斯•高斯林说:他和朋友经常去一个叫做圣米歇尔巷的地方吃午餐,据当地的传闻,感恩至死乐队在出名前经常在这里演出,其吉他手兼主唱杰瑞去世时,人们在这里祭奠他,所以称这里为死亡咖啡(cafe dead)。而那时候老爷子正在维护文件的编码格式,需要用到两个Magic Number,一个用于持久化对象,一个用于类文件。老爷子毫不迟疑的挑选cade dead做了持久化的Magic Number。当然类文件的Magic Number有着一样的开头:cafe(Java与咖啡的孽缘,哈哈哈哈嗝~),另一个Magic Number选择了babe(鬼知道老爷子干了什么)。不过正像dead的含义:过了不长时间,持久化对象就被抛弃了,取之而来的是RMI(远程方法调用[java版的RPC])。而类文件的Magic Number则一直保留到了现在。

    而后的四个字符是jdk的次要版本号:0000,再后面的0037,转化为十进制是55,是Java的主要版本号。Java版本号从45开始,jdk1.0、1.x的版本号都是45,每更新一个大版本,版本号+1而我的版本号是55,说明我的jdk版本是11。

    Java提供了反编译字节码的工具:javap。你可以使用javap -help获取它的用法。

用法: javap <options> <classes>
其中, 可能的选项包括:
  -? -h --help -help               输出此帮助消息
  -version                         版本信息
  -v  -verbose                     输出附加信息
  -l                               输出行号和本地变量表
  -public                          仅显示公共类和成员
  -protected                       显示受保护的/公共类和成员
  -package                         显示程序包/受保护的/公共类
                                   和成员 (默认)
  -p  -private                     显示所有类和成员
  -c                               对代码进行反汇编
  -s                               输出内部类型签名
  -sysinfo                         显示正在处理的类的
                                   系统信息 (路径, 大小, 日期, MD5 散列)
  -constants                       显示最终常量
  --module <模块>, -m <模块>       指定包含要反汇编的类的模块
  --module-path <路径>             指定查找应用程序模块的位置
  --system <jdk>                   指定查找系统模块的位置
  --class-path <路径>              指定查找用户类文件的位置
  -classpath <路径>                指定查找用户类文件的位置
  -cp <路径>                       指定查找用户类文件的位置
  -bootclasspath <路径>            覆盖引导类文件的位置

GNU 样式的选项可使用 = (而非空白) 来分隔选项名称
及其值。

每个类可由其文件名, URL 或其
全限定类名指定。示例:
   path/to/MyClass.class
   jar:file:///path/to/MyJar.jar!/mypkg/MyClass.class
   java.lang.Object

当你使用javap -verbose hello.class命令时,你将会得到下面的输出:

Classfile /home/fyatto/java/hello/hello.class
  Last modified 2020年3月24日; size 415 bytes
  MD5 checksum 18a31c9d516d29b597323d117d254a7a
  Compiled from "hello.java"
public class hello
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // hello
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello World
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // hello
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               hello.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello World
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               hello
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public hello();
    descriptor: ()V
    flags: (0x0001) 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

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) 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 World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
}
SourceFile: "hello.java"
  •     首先,开头的7行声明了Class文件的基本信息:所在位置、最后修改的时间、大小、MD5值、从哪个文件编译过来、类的全名、jdk次要版本号、jdk主要版本号,以及类的访问标志。

可以看到ACC_PUBLICACC_SUPER,像这样的访问标志共有八个:

标志名称标志值含义
ACC_SUPER0x0020是否使用invokespecial指令的新语义调用超类构造、初始化、私有、父类方法
ACC_FINAL0x0010是否被设置为final,仅类可设置
ACC_PUBLIC0x0001
ACC_INTERFACE0x0200
ACC_ABSTRACT0x0400
ACC_SYNTHETIC0x1000
ACC_ANNOTATION0x2000
ACC_ENUM0x4000


资料来源

维基百科:Java class file

最后编辑:2020年07月27日 ©著作权归作者所有

发表评论

正在加载 Emoji