Android Java8 Lambda 脱糖(ASM Hook 埋点)

8,645 阅读9分钟

Androi Studio 3.0及更高版本支持所有Java 7语言功能,以及部分Java 8语言功能(具体因平台版本而异)。

java8-api.png

Java 8语言功能通过默认工具链中的Desugar支持,但Java 8语言API需要跟随Android SDK的版本,Android 7.0 (Sdk 24) 开始提供相关API支持。

Desugar目前暂不支持MethodHandle.invokeMethodHandle.invokeExact,如果需要使用这两个方法,则需要指定miniSdkVersion 26或更高。

Retrolambda

Retrolambda通过JVM的一些机制(premain Agent ASM)在编译期把lambda表达式转换为内部类实现,但会生成类导致方法数增加。

Jack编译器

Google在Android SDK 21 (Android N 7.0)发布了新的编译器Jack/Jill来构建Android程序,但是他的实现机制是直接将Java源码转为dalvik字节码,而不是Java字节码。

随后Google发现这种实现方式带来的成本太大了,在Jack编译器之前Android程序的编译链是:

Java_Source-.Javac.->Java_ByteCode
Java_ByteCode-.dx.->dex

Jack后:

Java_Source-.Jack_Compiler.-dex

使用Jack直接将Java源码编译成了dex文件,Jack的推出是为了替代Javac的,Jill的作用是将依赖库.aar或者.jar转换成dex。

使用Jack,Google可以针对Android做更多的优化,而且还可以避免和Oracal的版权纠纷。Jack是Android 6.0-8.1的默认工具链,但google在Android 8.1之后废弃了Jack,原因原文

Over time, we realized the cost of switching to Jack was too high for our community when we considered the annotation processors, bytecode analyzers and rewriters impacted.

具体的原因无非是构建Java语言生态特性花费了大量的时间,比如注解处理器字节码分析重写和插桩工具,而且社区中已存在的基于这些基础设施的第三方库也无法平滑的切换到Jack。

题外话: APT注解处理器工具

在Android-Gradle-Tools 2.2版本前 Android没有提供默认的注解处理器,大部分工程都使用第三方开源的android-apt,在2.2版本之后,Google开始支持注解处理器annotationProcessor

D8编译器

Jack编译器弃用滞后,Google在Android 3.1引入了d8编译器

graph LR
Java_Source-.Javac.->Java_ByteCode
Java_ByteCode-.D8.->dex

Lambda表达式

public class Java8 {
    public static void main(String[] args) {
        new Thread(()->System.out.println("test")).start();
    }
}
javac java8.java //编译字节码

dx --dex --output=./Java8.dex Java8.class	//转换成dex文件
Uncaught translation error: com.android.dx.cf.code.SimException: ERROR in Java8.main:([Ljava/lang/String;)V: invalid opcode ba - invokedynamic requires --min-sdk-version >= 26 (currently 13)

Lambda表达式在Java字节码层面使用了invokedynamic指令,而Android对指令invokedynamic在sdk版本大于26才支持,但Android并未直接使用invokedynamic指令,而是在Android 8.0 (Sdk 26) 升级了Dex格式,通过两个新的Dalvik指令invoke-polymorphicinvoke-custom来提供动态调用的支持。

可在Dalvik字节码查看指令更多细节

下面比较一下生成的字节码文件

字节码文件

javap -verbose Java8	//查看字节码

Classfile /Users/gentrio/Desktop/Java8.class
  Last modified 2020-7-24; size 1043 bytes
  MD5 checksum 6ddd92fd44495106d002b3595320fbd5
  Compiled from "java8.java"
public class Java8
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#20        // java/lang/Object."<init>":()V
   #2 = Class              #21            // java/lang/Thread
   #3 = InvokeDynamic      #0:#26         // #0:run:()Ljava/lang/Runnable;
   #4 = Methodref          #2.#27         // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
   #5 = Methodref          #2.#28         // java/lang/Thread.start:()V
   #6 = Fieldref           #29.#30        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = String             #31            // test
   #8 = Methodref          #32.#33        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #9 = Class              #34            // Java8
  #10 = Class              #35            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               lambda$main$0
  #18 = Utf8               SourceFile
  #19 = Utf8               java8.java
  #20 = NameAndType        #11:#12        // "<init>":()V
  #21 = Utf8               java/lang/Thread
  #22 = Utf8               BootstrapMethods
  #23 = MethodHandle       #6:#36         // 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;
  #24 = MethodType         #12            //  ()V
  #25 = MethodHandle       #6:#37         // invokestatic Java8.lambda$main$0:()V
  #26 = NameAndType        #38:#39        // run:()Ljava/lang/Runnable;
  #27 = NameAndType        #11:#40        // "<init>":(Ljava/lang/Runnable;)V
  #28 = NameAndType        #41:#12        // start:()V
  #29 = Class              #42            // java/lang/System
  #30 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #31 = Utf8               test
  #32 = Class              #45            // java/io/PrintStream
  #33 = NameAndType        #46:#47        // println:(Ljava/lang/String;)V
  #34 = Utf8               Java8
  #35 = Utf8               java/lang/Object
  #36 = Methodref          #48.#49        // 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;
  #37 = Methodref          #9.#50         // Java8.lambda$main$0:()V
  #38 = Utf8               run
  #39 = Utf8               ()Ljava/lang/Runnable;
  #40 = Utf8               (Ljava/lang/Runnable;)V
  #41 = Utf8               start
  #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
  #48 = Class              #51            // java/lang/invoke/LambdaMetafactory
  #49 = NameAndType        #52:#56        // 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;
  #50 = NameAndType        #17:#12        // lambda$main$0:()V
  #51 = Utf8               java/lang/invoke/LambdaMetafactory
  #52 = Utf8               metafactory
  #53 = Class              #58            // java/lang/invoke/MethodHandles$Lookup
  #54 = Utf8               Lookup
  #55 = Utf8               InnerClasses
  #56 = Utf8               (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;
  #57 = Class              #59            // java/lang/invoke/MethodHandles
  #58 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #59 = Utf8               java/lang/invoke/MethodHandles
{
  public Java8();
    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

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #2                  // class java/lang/Thread
         3: dup
         4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
        12: invokevirtual #5                  // Method java/lang/Thread.start:()V
        15: return
      LineNumberTable:
        line 4: 0
        line 5: 15
}
SourceFile: "java8.java"
InnerClasses:
     public static final #54= #53 of #57; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #23 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;
    Method arguments:
      #24 ()V
      #25 invokestatic Java8.lambda$main$0:()V
      #24 ()V

通过d8编译出来的dex文件

Processing 'classes.dex'...
Opened 'classes.dex', DEX version '035'
Class #0            -
  Class descriptor  : 'L-?Lambda$Java8$90vfg5nki1Mk2vmViVKTcPvg1UM;'
  Access flags      : 0x1011 (PUBLIC FINAL SYNTHETIC)
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
    #0              : 'Ljava/lang/Runnable;'
  Static fields     -
    #0              : (in L-?Lambda$Java8$90vfg5nki1Mk2vmViVKTcPvg1UM;)
      name          : 'INSTANCE'
      type          : 'L-?Lambda$Java8$90vfg5nki1Mk2vmViVKTcPvg1UM;'
      access        : 0x1019 (PUBLIC STATIC FINAL SYNTHETIC)
  Instance fields   -
  Direct methods    -
    #0              : (in L-?Lambda$Java8$90vfg5nki1Mk2vmViVKTcPvg1UM;)
      name          : '<clinit>'
      type          : '()V'
      access        : 0x11008 (STATIC SYNTHETIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 0
      outs          : 1
      insns size    : 8 16-bit code units
      catches       : (none)
      positions     :
      locals        :
    #1              : (in L-?Lambda$Java8$90vfg5nki1Mk2vmViVKTcPvg1UM;)
      name          : '<init>'
      type          : '()V'
      access        : 0x11002 (PRIVATE SYNTHETIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 1
      outs          : 1
      insns size    : 4 16-bit code units
      catches       : (none)
      positions     :
      locals        :
  Virtual methods   -
    #0              : (in L-?Lambda$Java8$90vfg5nki1Mk2vmViVKTcPvg1UM;)
      name          : 'run'
      type          : '()V'
      access        : 0x0011 (PUBLIC FINAL)
      code          -
      registers     : 1
      ins           : 1
      outs          : 0
      insns size    : 4 16-bit code units
      catches       : (none)
      positions     :
      locals        :
  source_file_idx   : 15 (lambda)

Class #1            -
  Class descriptor  : 'LJava8;'
  Access flags      : 0x0001 (PUBLIC)
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
  Static fields     -
  Instance fields   -
  Direct methods    -
    #0              : (in LJava8;)
      name          : '<init>'
      type          : '()V'
      access        : 0x10001 (PUBLIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 1
      outs          : 1
      insns size    : 4 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=1
      locals        :
        0x0000 - 0x0004 reg=0 this LJava8;
    #1              : (in LJava8;)
      name          : 'lambda$main$0'
      type          : '()V'
      access        : 0x1008 (STATIC SYNTHETIC)
      code          -
      registers     : 2
      ins           : 0
      outs          : 2
      insns size    : 8 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=4
      locals        :
    #2              : (in LJava8;)
      name          : 'main'
      type          : '([Ljava/lang/String;)V'
      access        : 0x0009 (PUBLIC STATIC)
      code          -
      registers     : 2
      ins           : 1
      outs          : 2
      insns size    : 11 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=4
        0x000a line=5
      locals        :
  Virtual methods   -
  source_file_idx   : 14 (java8.java)

通过dx指定min-sdk-version=26编译出的dex文件

dexdump Java8-dx.dex

Processing 'Java8.dex'...
Opened 'Java8.dex', DEX version '038'
Class #0            -
  Class descriptor  : 'LJava8;'
  Access flags      : 0x0001 (PUBLIC)
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
  Static fields     -
  Instance fields   -
  Direct methods    -
    #0              : (in LJava8;)
      name          : '<init>'
      type          : '()V'
      access        : 0x10001 (PUBLIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 1
      outs          : 1
      insns size    : 4 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=1
      locals        :
        0x0000 - 0x0004 reg=0 this LJava8;
    #1              : (in LJava8;)
      name          : 'lambda$main$0'
      type          : '()V'
      access        : 0x100a (PRIVATE STATIC SYNTHETIC)
      code          -
      registers     : 2
      ins           : 0
      outs          : 2
      insns size    : 8 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=4
      locals        :
    #2              : (in LJava8;)
      name          : 'main'
      type          : '([Ljava/lang/String;)V'
      access        : 0x0009 (PUBLIC STATIC)
      code          -
      registers     : 3
      ins           : 1
      outs          : 2
      insns size    : 13 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=4
        0x000c line=5
      locals        :
  Virtual methods   -
  source_file_idx   : 18 (java8.java)

Method Handle #0:
  type        : invoke-static
  target      : LJava8; lambda$main$0
  target_type : ()V
Method Handle #1:
  type        : invoke-static
  target      : Ljava/lang/invoke/LambdaMetafactory; metafactory
  target_type : (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;
Call Site #0 // offset 1059
  link_argument[0] : 1 (MethodHandle)
  link_argument[1] : run (String)
  link_argument[2] : ()Ljava/lang/Runnable; (MethodType)
  link_argument[3] : ()V (MethodType)
  link_argument[4] : 0 (MethodHandle)
  link_argument[5] : ()V (MethodType)

目前Android通过脱糖(Desugaring)的方式来实现所有版本设备的Lambda函数的支持。

脱糖 即在编译阶段将语法层面一些底层字节码不支持的特性转换成基础的字节码结构,(比如List上的泛型脱糖后在字节码层面实际为Object;

通过Java字节码可以发现Lambda在Jvm上是通过invokedynamic指令来实现的,但在dex字节码文件中却没有发现invokedynamic指令,下面分析一下jvmdalvik上的区别:

invokedynamic指令

invokedynamic指令是Java 7新增的字节码调用指令,作为Java支持动态类型语言的改进之一,跟invokevirtualinvokestaticinvokeinterfaceinvokespecial指令构成了虚拟机层面的Java方法分配调用指令集。

  • 后四种指令,在编译期间生成的class文件中,通过常量池(Constant Pool)的MethodRef常量已经固定了目标方法的符号信息,虚拟机使用符号信息能直接解释出具体的方法,直接调用。
  • 而invokedynamic指令在编译期间生成的class文件中,对应常量池(Constant Pool)的invokedynamic_Info常量存储的符号信息中并没有方法所述者以及其类型,替代的是BoostrapMethod信息。在运行时,通过引导方法BootstrapMethod机制动态确定方法的所述者和类型。
Constant pool:
   #1 = Methodref          #10.#20        // java/lang/Object."<init>":()V
   #2 = Class              #21            // java/lang/Thread
   #3 = InvokeDynamic      #0:#26         // #0:run:()Ljava/lang/Runnable;
   #4 = Methodref          #2.#27         // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
   #5 = Methodref          #2.#28         // java/lang/Thread.start:()V
   #6 = Fieldref           #29.#30        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = String             #31            // test
   #8 = Methodref          #32.#33        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #9 = Class              #34            // Java8
  #10 = Class              #35            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               lambda$main$0
  #18 = Utf8               SourceFile
  #19 = Utf8               java8.java
  #20 = NameAndType        #11:#12        // "<init>":()V
  #21 = Utf8               java/lang/Thread
  #22 = Utf8               BootstrapMethods
  #23 = MethodHandle       #6:#36         // invokestatic 
                           
  ………………………………………………………………………………………………
  
  BootstrapMethods:
  0: #23 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;
    Method arguments:
      #24 ()V
      #25 invokestatic Java8.lambda$main$0:()V
      #24 ()V

分析字节码可知,invokedynamic的调用步骤,编译时生成私有静态方法 -> 通过invokedynamic指令调用LambdaMetafactory.metafactory -> LambdaMetafactory.metafactory在内存中动态生成一个实现Lambda表达式对应函数式接口的实例并在实现中调用生成的私有静态方法

重点就在于LambdametaFactory.metafactory,通过查看Android SDK发现虽然在Android 7.0 SDK 24以后,Android运行时SDK中存在LambdametaFactory但却是空实现

package java.lang.invoke;

public class LambdaMetafactory {

    public static final int FLAG_SERIALIZABLE = 1 << 0;

    public static final int FLAG_MARKERS = 1 << 1;

    public static final int FLAG_BRIDGES = 1 << 2;

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException { return null; }

    public static CallSite altMetafactory(MethodHandles.Lookup caller,
                                          String invokedName,
                                          MethodType invokedType,
                                          Object... args)
            throws LambdaConversionException { return null; }
}

为了验证LambdaMetafactory在运行时有没有具体实现,尝试在Android 10 SDK 29的机器上运行通过指定min-sdk-version的dx打出的dex包。

adb push Java8.dex /sdcard/
adb shell dalvikvm -cp /sdcard/Java8.dex Java8

Exception in thread "main" java.lang.BootstrapMethodError: Exception from call site #0 bootstrap method
	at Java8.main(java8.java:4)
Caused by: java.lang.ClassCastException: Bootstrap method returned null
	... 1 more

因此其实即使指定min-sdk-version为26,通过dx打出来的包,也是不能运行的,因为dx通过同javac一样是通过invokedynamic来调用LambdaMetafactory.metafactory来实现lambda的动态调用,而目前最新的Android版本也没有其具体实现,目前唯一的解决方案就是通过脱糖来支持Lambda表达式。

至于RetroLambdaJackD8的原理都是一致的,都是生成Labmda接口的实例类型,再通过类型调用具体实现即javac生成的私有静态方法,他们的区别在于脱糖的时机不同:

  • RetroLambda通过Transform在javac编译之后,dex编译之前。
  • Jack在Jack编译过程中处理输出为dex,不经过javac,直接source to dex
  • D8通过D8的编译过程中处理输出为dex,发生在javac之后,具体的gradle task transformDexArchiveWithDexMergerForDebug生成的dex在build/intermediates/transforms/dexMerger

Hook Lambda

方法一

Android Studio 3.1默认使用d8 编译器,而脱糖操作在d8当中,无法找到Hook点,所以通过关闭d8的脱糖功能,使用desugarTransform来进行脱糖。

关闭d8脱糖功能,通过 gradle.properties 配置 android.enableD8.desugaring=false 来关闭。

方法二

通过 invokedynamic 指令查找Lambda方法句柄,替换成生成的Hook方法,再在Hook方法中实现 invokedynamic 指令中的代码逻辑。

实现思路:有两种API实现方式Tree API或者Visitor API,Visitor API使用 MethodVisitor 中的 visitInvokeDynamicInsn 方法来实现,Tree API使用 MethodNode 通过访问 instructions 当中的 InvokeDynamicInsnNode

这里使用Visitor API实现,实例代码如下:

class CustomClassNode(cv: ClassVisitor) : ClassVisitor(Opcodes.ASM7, cv) {

    private var className: String? = null

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        this.className = name
        super.visit(version, access, name, signature, superName, interfaces)
    }

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<String>?
    ): MethodVisitor {
        return CustomMethodVisitor(
            this.className,
            cv,
            super.visitMethod(access, name, descriptor, signature, exceptions)
        )
    }
}

class CustomMethodVisitor(private val className: String?, private val cv: ClassVisitor, methodVisitor: MethodVisitor) :
    MethodVisitor(Opcodes.ASM7, methodVisitor) {

    override fun visitInvokeDynamicInsn(
        name: String?,
        descriptor: String?,
        bootstrapMethodHandle: Handle?,
        vararg bootstrapMethodArguments: Any?
    ) {
        val argumentList = bootstrapMethodArguments.toMutableList()
        // 函数式接口描述符
        val descriptorType: Type = Type.getType(descriptor)
        // 方法参数描述符
        val methodType: Type? = argumentList[0] as? Type
        // 实现方法参数描述符
        val methodImplType: Type? = argumentList[2] as? Type
        // 方法描述描述符
        val methodDesc: String? = methodType?.descriptor
        // 方法签名
        val methodNameAndDesc: String = name + methodDesc
      
	//---------------------函数式接口过滤-----------------------

        // 函数式接口参数描述符
        val type: Array<Type> = descriptorType.argumentTypes
        val middleMethodDesc = if (type.isEmpty()) {
            methodImplType?.descriptor
        } else {
            "(" + type.joinToString {
                it.descriptor
            } + methodImplType?.descriptor?.replace("(", "")
        }
        val middleMethodName = "lambda${'$'}${name}${'$'}trace0"

        // invokeDynamic原先的MethodHandle
        val oldMethodHandle = argumentList[1] as? Handle
        // 要替换的MethodHandle
        val newMethodHandle = Handle(Opcodes.H_INVOKESTATIC, className, middleMethodName, middleMethodDesc, false)
        argumentList[1] = newMethodHandle

        // 生成的中间方法
        val methodNode =
            MethodNode(Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC, middleMethodName, middleMethodDesc, null, null)
        methodNode.visitCode()
        val opcode = when (oldMethodHandle?.tag) {
            Opcodes.H_INVOKEINTERFACE -> Opcodes.INVOKEINTERFACE
            Opcodes.H_INVOKESPECIAL -> Opcodes.INVOKESPECIAL
            Opcodes.H_NEWINVOKESPECIAL -> {
                methodNode.visitTypeInsn(Opcodes.NEW, oldMethodHandle.owner)
                methodNode.visitInsn(Opcodes.DUP)
                Opcodes.INVOKESPECIAL
            }
            Opcodes.H_INVOKESTATIC -> Opcodes.INVOKESTATIC
            Opcodes.H_INVOKEVIRTUAL -> Opcodes.INVOKEVIRTUAL
            else -> 0
        }

        val middleMethodType = Type.getType(middleMethodDesc)
        val argumentType = middleMethodType.argumentTypes
        if (argumentType.isNotEmpty()) {
            var loadIndex = 0
            argumentType.forEach {
                methodNode.visitVarInsn(it.getOpcode(Opcodes.ILOAD), loadIndex)
                loadIndex += it.size
            }
        }
      
      	//---------------------Hook点可插入自定义方法-----------------------

        //调用原来的lambda实现
        methodNode.visitMethodInsn(opcode, oldMethodHandle?.owner, oldMethodHandle?.name, oldMethodHandle?.desc, false)
        val returnType = middleMethodType.returnType
        val returnOpcodes = returnType.getOpcode(Opcodes.IRETURN)
        methodNode.visitInsn(returnOpcodes)
        methodNode.visitEnd()
        methodNode.accept(this.cv)
        super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, *argumentList.toTypedArray())
    }
}

反编译原来的字节码和脱糖后的字节码

//源码
public class Java8 {
    public Java8() {
    }

    public static void main(String[] args) {
        (new Thread(() -> {
            System.out.println("test");
        })).start();
    }
}

//脱糖后
public class Java8 {
    public Java8() {
    }

    public static void main(String[] args) {
        (new Thread(Java8::lambda$run$trace0)).start();
    }

    private static void lambda$run$trace0() {
        lambda$main$0();
    }
}