众所周知, Java 源文件会被编译为 .class 字节码文件, 然后交由 JVM 虚拟机执行. .class 文件相对于 .java 文件有着更强的表达性和结构性(更紧凑), 所以对 JVM 来说, .class 文件才是 “源码”.

今天, 我们就来深入浅出的看看 .class 文件的奥秘.

动手看一看

Untitled

源文件

首先, 有个 Java 源码文件:

// Main.java

package com.walfud;

import jdk.jfr.Label;
import jdk.jfr.Name;

public class Main implements Runnable {

    @Name("foo")
    @Label("bar")
    public static String s = "Hello, World!";

    public int i = 2;

    public static void main() {
        System.out.println(s);
    }

    public void run() {
    }

    public void foo(int j) throws RuntimeException {
        String s = "";

        try {
            s = "foo";
            throw new RuntimeException();
        } catch (Exception e) {
            // nothing
            s = "bar";
        } finally {
            s = s + ";";
        }

        System.out.println("done");
    }
}

javac

javac 是编译器, 将 java 源码文件编译为 .class 字节码文件.

# shell
javac Main.java

默认情况下 .class 文件只包含有限的调试信息, 比如代码行号和文件名. 可以使用 -g 参数要求编译器生成所有调试信息, -g:none 则不包含任何调试信息. --release 可以指定编译所使用的 JDK 版本, 例如 8, 11, 17 等.

.class 文件是个二进制文件, 有两种方法查看:

1. 使用二进制查看

一种是直接打开二进制文件, 这里我们推荐 010 Editor 编辑器(跨平台, 支持各种模板, 可以解析 .class 文件).

过期后, Linux 下可以通过 rm -rf ~/.local/share/SweetScape/ 重置.

Untitled

2. 使用 javap 查看

另一种方法是通过 JDK 自带的 javap 命令, 他会自动解析 .class 文件, 将其中的各个字段格式化输出.

# shell
javap -v Main.class

-v 用于输出详细信息, 会包含 .class 文件中的所有信息.

Untitled

为了探究 .class 文件的本质, 本文我们直接观察二进制文件方式来带大家解读其中的奥秘.

Class 文件结构

class 文件是一个结构化的二进制数据流, 整个文件以大端序进行组织. 文件的结构如下:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

.class 数据布局 (图片来自: https://blog.csdn.net/qq_43843037/article/details/107926711. 如有侵权请联系作者删除)

.class 数据布局 (图片来自: https://blog.csdn.net/qq_43843037/article/details/107926711. 如有侵权请联系作者删除)

.class 文件有两种主要的数据类型: 一种是定长数据, 由 u1/u2/u4 分别代表无符号的 1/2/4 字节数据, 一般数据区的长度和各种位标记(bit flag) 都使用这种结构表示. 另一种是变长数据, 他们前 2 字节表示 length, 后面则是实际的数据. 例如 u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; 这两个字段一起表示常量池的内容.