bigywater.tech
Java · 269

javap:从类文件反推 Java 编译结果

源码和实际运行的字节码之间隔着 javac 的「脱糖」。字符串 switch、枚举、字符串拼接、lambda、finally 到底变成了什么?这篇用 javap 逐个编译、逐个反汇编,看清每一种写法背后的真实字节码。

4 vs 13 条
字符串拼接的指令数:invokedynamic(默认)对 StringBuilder(inline)
ordinal()
JDK 21 里同一编译单元的枚举 switch 直接使用的方法(没有 $SwitchMap 合成类)
2 份
finally 块里的 println 在字节码里被复制的份数

类头、常量池与调试信息

实测 · 本机执行输出
  javap -v 的头部: ['minor version: 0', 'major version: 65', 'flags: (0x0021) ACC_PUBLIC, ACC_SUPER', 'this_class: #89 // T', 'super_class: #2 // java/lang/Object']
  常量池 252 项,按类型: {'Utf8': 136, 'NameAndType': 34, 'Class': 25, 'Methodref': 22, 'String': 14, 'InterfaceMethodref': 7, 'MethodHandle': 5, 'InvokeDynamic': 4}
  int add(int a, int b) { int s = a + b; return s * 2; } 的字节码: iload_1 ; iload_2 ; iadd ; istore_3 ; iload_3 ; iconst_2 ; imul ; ireturn
  默认                 LineNumberTable 19 处,LocalVariableTable 0 处,MethodParameters 0 处;文件大小 4861 字节
  javac -g           LineNumberTable 19 处,LocalVariableTable 16 处,MethodParameters 0 处;文件大小 5632 字节
  javac -parameters  LineNumberTable 20 处,LocalVariableTable 0 处,MethodParameters 13 处;文件大小 5065 字节

switch 的脱糖

实测 · 本机执行输出
  字符串 switch: aload_0 ; astore_1 ; iconst_m1 ; istore_2 ; aload_1 ; invokevirtual #7                  // Method java/lang/String.hashCode:()I ; lookupswitch { // 2 ; 50 ; 36 ; aload_1 ; ldc #13                 // String alpha ; invokevirtual #15                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z ; ifeq 61 ; iconst_0 …
  字符串 switch 里调用 hashCode / equals / lookupswitch 的次数: 1 2 2
  枚举 switch: aload_0 ; invokevirtual #27                 // Method T$Color.ordinal:()I ; lookupswitch { // 2 ; 32 ; 34 ; iconst_1 ; ireturn ; iconst_2 …
  枚举 switch 生成的合成类: ['T$1Local.class', 'T$Color.class', 'T$Point.class']
  模式 switch: invokedynamic #38,  0             // InvokeDynamic #0:typeSwitch:(Ljava/lang/Object;I)I ; tableswitch { // 0 to 2 ; checkcast #42                 // class java/lang/Integer ; checkcast #42                 // class java/lang/Integer ; checkcast #8                  // class java/lang/String …
  BootstrapMethods 里的引导方法: ['LambdaMetafactory.metafactory', 'StringConcatFactory.makeConcatWithConstants', 'SwitchBootstraps.typeSwitch']
  record Point 的 toString 字节码: aload_0 ; invokedynamic #16,  0             // InvokeDynamic #0:toString:(LT$Point;)Ljava/lang/String; ; areturn

字符串拼接

实测 · 本机执行输出
  JDK 21 javac 默认: aload_0 ; iload_1 ; invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConsta ; areturn
  javac -XDstringConcat=inline: new #7                  // class java/lang/StringBuild ; dup ; invokespecial #9                  // Method java/lang/StringBuil ; ldc #10                 // String a= ; invokevirtual #12                 // Method java/lang/StringBuil ; aload_0 ; invokevirtual #12                 // Method java/lang/StringBuil ; ldc #16                 // String ,b= ; invokevirtual #12                 // Method java/lang/StringBuil ; iload_1 ; invokevirtual #18                 // Method java/lang/StringBuil ; invokevirtual #21                 // Method java/lang/StringBuil ; areturn
  javac --release 8: 13 条指令,invokedynamic 0 次,invokevirtual StringBuilder.append 4 次
  三种写法的指令数:默认 4,inline 13,--release 8 13;class 文件大小:默认 685,inline 497,--release 8 497 字节
  invokedynamic 的方法类型与配方: ['Ljava/lang/String;I', 'Ljava/lang/String;I'] []

lambda、assert、finally 与锁

实测 · 本机执行输出
  方法签名里的合成/生成成员: ['static final boolean $assertionsDisabled;', 'private static void lambda$twr$1() throws java.lang.Exception;', 'private static int lambda$lambda$0(int, java.lang.Integer);']
  lambda 所在方法里: invokeinterface #59,  1           // InterfaceMethod java/util/List.stream:()Ljava/uti ; iload_1 ; invokedynamic #65,  0             // InvokeDynamic #2:applyAsInt:(I)Ljava/util/funct ; invokeinterface #69,  2           // InterfaceMethod java/util/stream/Stream.mapToInt: ; invokeinterface #75,  1           // InterfaceMethod java/util/stream/IntStream.sum:()
  lambda 体被编译成的私有静态方法: ['private static void lambda$twr$1() throws java.lang.Exception', 'private static int lambda$lambda$0(int, java.lang.Integer)']
  内部类文件: ['T$1Local.class', 'T$Color.class', 'T$Point.class']
  assert 语句: getstatic #88                 // Field $assertions ; ifne 20 ; iload_0 ; ifgt 20 ; new #94                 // class java/lang/A ; dup ; ldc #96                 // String x must be  …
  装箱/拆箱: ['#44                 // Method java/lang/Integer.intValue:()I', '#115                // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;', '#44                 // Method java/lang/Integer.intValue:()I']
  try/finally(finally 里的 println 被复制到几处):println 调用 2 处;athrow 1 个;字节码 14 条
  synchronized:monitorenter 1 条,monitorexit 2 条(正常路径与异常路径各一个)
  try-with-resources:close() 调用 3 处;addSuppressed 1 处

分步演示

javap
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 的 javac 与 javap;被分析的是自己写的一个小类 T
    • 没有验证:其它编译器(ECJ、Kotlin)的输出、枚举在不同编译单元里的 $SwitchMap 形式(这里的枚举与 switch 在同一个类里)、-XDstringConcat 的其它取值、JIT 之后的实际执行(javap 看的是字节码,不是机器码)、局部变量槽的完整分配细节
    京ICP备2026067502号-1京公网安备11010502063487号