Java ASM系列:(067)Java 8 Lambda原理探究

Posted lsieun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java ASM系列:(067)Java 8 Lambda原理探究相关的知识,希望对你有一定的参考价值。

本文属于Java ASM系列二:OPCODE当中的一篇。


对于《Java ASM系列二:OPCODE》有配套的视频讲解,可以点击这里这里进行查看;同时,也可以点击这里查看源码资料。


在本文当中,我们主要对Java 8 Lambda的两方面进行介绍:

  • 第一方面,如何使用ASM生成Lambda表达式?
  • 第二方面,探究Lambda表达式的实现原理是什么?

1. 使用ASM生成Lambda

1.1. 预期目标

我们的预期目标是生成一个HelloWorld类,代码如下:

import java.util.function.BiFunction;

public class HelloWorld {
    public void test() {
        BiFunction<Integer, Integer, Integer> func = Math::max;
        Integer result = func.apply(10, 20);
        System.out.println(result);
    }
}

1.2. 编码实现

在下面的代码中,我们重点关注两个点:

  • 第一点,是Handle实例的创建。
  • 第二点,是MethodVisitor.visitInvokeDynamicInsn()方法。
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.*;

public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            mv2.visitCode();
            // 第1点,Handle实例的创建
            Handle bootstrapMethodHandle = new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
                    "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;" +
                            "Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false);
            // 第2点,MethodVisitor.visitInvokeDynamicInsn()方法的调用
            mv2.visitInvokeDynamicInsn("apply", "()Ljava/util/function/BiFunction;",
                    bootstrapMethodHandle,
                    Type.getType("(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
                    new Handle(H_INVOKESTATIC, "java/lang/Math", "max", "(II)I", false),
                    Type.getType("(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;")
            );
            mv2.visitVarInsn(ASTORE, 1);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitIntInsn(BIPUSH, 10);
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            mv2.visitIntInsn(BIPUSH, 20);
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            mv2.visitMethodInsn(INVOKEINTERFACE, "java/util/function/BiFunction", "apply",
                    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
            mv2.visitTypeInsn(CHECKCAST, "java/lang/Integer");
            mv2.visitVarInsn(ASTORE, 2);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitVarInsn(ALOAD, 2);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

1.3. 验证结果

接下来,我们来验证生成的Lambda表达式是否能够正常运行。

import java.lang.reflect.Method;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method m = clazz.getDeclaredMethod("test");
        m.invoke(obj);
    }
}

2. 探究Lambda的实现原理

简单来说,Lambda表达式的内部原理,是借助于Java ASM生成匿名内部类来实现的。

2.1. 追踪Lambda

首先,我们使用javap -v -p sample.HelloWorld命令查看输出结果:

$ javap -v -p sample.HelloWorld
...
BootstrapMethods:
  0: #28 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(
        Ljava/lang/invoke/MethodHandles$Lookup;
        Ljava/lang/String;Ljava/lang/invoke/MethodType;
        Ljava/lang/invoke/MethodType;
        Ljava/lang/invoke/MethodHandle;
        Ljava/lang/invok e/MethodType;
      )Ljava/lang/invoke/CallSite;
    Method arguments:
      #29 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      #30 invokestatic java/lang/Math.max:(II)I
      #31 (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;

通过上面的输出结果,我们定位到BootstrapMethods的部分,可以看到它使用了java.lang.invoke.LambdaMetafactory类的metafactory()方法。

至此,我们想表达的意思:Lambda表达式是与LambdaMetafactory.metafactory()方法有关联关系的。


在IDE当中,我们可以查看LambdaMetafactory.metafactory()方法,其内容如下:

在上图中,我们可以看到mf指向一个InnerClassLambdaMetafactory的实例,并在最后调用了buildCallSite()方法。


接着,我们跳转到java.lang.invoke.InnerClassLambdaMetafactory类的buildCallSite()方法

java.lang.invoke.InnerClassLambdaMetafactory类的spinInnerClass()方法中,找到如下代码:

final byte[] classBytes = cw.toByteArray();

在其语句上,打一个断点:

调试运行,然后使用如下表达式来查看classBytes的值:

Arrays.toString(classBytes)

其实,这里的classBytes就是生成的类文件的字节码内容,这样的字符串内容就是该字节码内容的另一种表现形式。那么,拿到这样一个字符串内容之后,我们应该如何处理呢?

2.2. 查看生成的类

项目代码中,有一个LambdaRun类,它的作用就是将上述字符串内容的类信息打印出来。

import lsieun.utils.StringUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.IOException;
import java.io.PrintWriter;

public class LambdaRun {
    public static void main(String[] args) throws IOException {
        // (1) 设置参数
        String str = "[-54, -2, -70, -66, ...]";
        byte[] bytes = StringUtils.array2Bytes(str);
        int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;

        // (2) 打印结果
        Printer printer = new Textifier();
        PrintWriter printWriter = new PrintWriter(System.out, true);
        TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);
        new ClassReader(bytes).accept(traceClassVisitor, parsingOptions);
    }
}

我们可以将代表字节码内容的字符串放到上面代码的str变量中,然后运行LambdaRun可以得到如下结果:

// class version 52.0 (52)
// access flags 0x1030
final synthetic class sample/HelloWorld$$Lambda$1 implements java/util/function/BiFunction {

  // access flags 0x2
  private <init>()V
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  @Ljava/lang/invoke/LambdaForm$Hidden;()
    ALOAD 1
    CHECKCAST java/lang/Integer
    INVOKEVIRTUAL java/lang/Integer.intValue ()I
    ALOAD 2
    CHECKCAST java/lang/Integer
    INVOKEVIRTUAL java/lang/Integer.intValue ()I
    INVOKESTATIC java/lang/Math.max (II)I
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ARETURN
    MAXSTACK = 2
    MAXLOCALS = 3
}

通过上面的输出结果,我们可以看到:

  • 第一点,当前类的名字叫sample/HelloWorld$$Lambda$1
    • 当前类带有finalsynthetic标识。
    • 当前类实现了java/util/function/BiFunction接口。
  • 第二点,在sample/HelloWorld$$Lambda$1类当中,它定义了一个构造方法(&lt;init&gt;()V)。
  • 第三点,在sample/HelloWorld$$Lambda$1类当中,它定义了一个apply方法(apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;)。
    • 这个apply方法正是BiFunction接口中定义的方法。
    • apply方法的内部代码逻辑,是通过调用Math.max()方法来实现的;而Math::max正是Lambda表达式的内容。

至此,我们可以知道:Lambda表达式的实现,是由JVM调用ASM来实现的。也就是说,JVM使用ASM创建一个匿名内部类(sample/HelloWorld$$Lambda$1),让该匿名内部类实现特定的接口(java/util/function/BiFunction),并在接口定义的方法(apply)实现中包含Lambda表达式的内容。

以上是关于Java ASM系列:(067)Java 8 Lambda原理探究的主要内容,如果未能解决你的问题,请参考以下文章

Java ASM系列:(057)opcode: array

Java ASM系列:(053)opcode: math

Java ASM系列:(051)opcode: constant

Java ASM系列:(058)opcode: jump

Java ASM系列:(056)opcode: method

Java ASM系列:(059)opcode: stack