preface

We often encounter anonymous inner classes in our daily development, and anonymous inner classes hold references to external classes. What about the bytecode level? This article looks at inner classes and LAMda at the bytecode level


Anonymous inner classes

public class NoNameInnerClass {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run(a) {}}; r.run(); }}Copy the code

We created a new runable and called the run method. Now we’re going to class file the above class through Javac, We see two class files, NoNameInnerClass$1.class and nonameinnerclass.class, generated when compiled.

Let’s look at NoNameInnerClass$1.class and nonameinnerclass.class

//NoNameInnerClass$1.class
final class NoNameInnerClassThe $1implements Runnable {
    NoNameInnerClass$1() {}public void run(a) {}}//NoNameInnerClass.class
public class NoNameInnerClass {
    public NoNameInnerClass(a) {}public static void main(String[] var0) {
        Runnable var1 = new Runnable() {
            public void run(a) {}}; var1.run(); }}Copy the code

It’s intuitively obvious that we’re generating an extra $1 NoNameInnerClass, but we’re not using it in the NoNameInnerClass, so let’s look at the bytecode for the NoNameInnerClass

public class javaplan.NoNameInnerClass {
  // The default constructor for NoNameInnerClass
  public javaplan.NoNameInnerClass();
    Code:
       0: aload_0
       1: invokespecial #1   // Method java/lang/Object."<init>":()V
       4: return
 / / the main method
  public static void main(java.lang.String[]);
    Code:
       // New a NoNameInnerClass$1 and call the NoNameInnerClass$1 constructor
       0: new           #2   // class javaplan/NoNameInnerClass$1
       3: dup
       4: invokespecial #3  // Method javaplan/NoNameInnerClass$1."<init>":()V
       7: astore_1
       Call the NoNameInnerClass$1 run method
       8: aload_1
       9: invokeinterface #4.1  // InterfaceMethod java/lang/Runnable.run:()V
      14: return
}
Copy the code

At the bytecode level, an instance of NoNameInnerClass$1 is generated and the run method is called. Why else would an extra class file be generated for no reason?


Second, the lambda

With the above steps, we write a simple lamda

public class LamdaTest {
    public static void main(String[] args) { Runnable r = ()->{ }; r.run(); }}Copy the code

We then compile the class file using javac and find, surprisingly, that there is no extra class file generated, just a LamdaTest. Class

Lamdatest.class bytecode: javap-c: javap-c: javap-c: javap-c: javap-c

public class javaplan.LamdaTest
  //
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref          #5.#15         // java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0: #20         // #0:run:()Ljava/lang/Runnable;
   #3 = InterfaceMethodref #21.#22        // java/lang/Runnable.run:()V
   #4 = Class              #23            // javaplan/LamdaTest
   #5 = Class              #24            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               lambda$main$0
  #13 = Utf8               SourceFile
  #14 = Utf8               LamdaTest.java
  #15 = NameAndType        #6: #7          // "<init>":()V
  #16 = Utf8               BootstrapMethods
  #17 = MethodHandle       #6: #25         // 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;
  #18 = MethodType         #7             // ()V
  #19 = MethodHandle       #6: #26         // invokestatic javaplan/LamdaTest.lambda$main$0:()V
  #20 = NameAndType        #27: #28        // run:()Ljava/lang/Runnable;
  #21 = Class              #29            // java/lang/Runnable
  #22 = NameAndType        #27: #7         // run:()V
  #23 = Utf8               javaplan/LamdaTest
  #24 = Utf8               java/lang/Object
  #25 = Methodref          #30.#31        // 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;
  #26 = Methodref          #4.#32         // javaplan/LamdaTest.lambda$main$0:()V
  #27 = Utf8               run
  #28 = Utf8               ()Ljava/lang/Runnable;
  #29 = Utf8               java/lang/Runnable
  #30 = Class              #33            // java/lang/invoke/LambdaMetafactory
  #31 = NameAndType        #34: #38        // 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;
  #32 = NameAndType        #12: #7         // lambda$main$0:()V
  #33 = Utf8               java/lang/invoke/LambdaMetafactory
  #34 = Utf8               metafactory
  #35 = Class              #40            // java/lang/invoke/MethodHandles$Lookup
  #36 = Utf8               Lookup
  #37 = Utf8               InnerClasses
  #38= 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; #39 = Class              #41            // java/lang/invoke/MethodHandles
  #40 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #41 = Utf8               java/lang/invoke/MethodHandles
{
  public javaplan.LamdaTest();
    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 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: invokedynamic #2.0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         5: astore_1
         6: aload_1
         7: invokeinterface #3.1            // InterfaceMethod java/lang/Runnable.run:()V
        12: return
      LineNumberTable:
        line 5: 0
        line 8: 6
        line 9: 12

  private static void lambda$main$0(a); descriptor: ()V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 7: 0
}
SourceFile: "LamdaTest.java"
InnerClasses:
     public static final #36= #35 of #39; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #17invokestatic 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: #18 ()V
      #19 invokestatic javaplan/LamdaTest.lambda$main$0:()V
      #18 ()V
Copy the code

You can see that the Invokedynamic instruction (specifically for LAMda) is invoked, pointing to constant pool #2;

Constant pool #2 points to #0, which corresponds to a special lookup called BootstrapMethods:

BootstrapMethods: The MetaFactory method is first called with Invokestatic

The arguments:

Take a look at this method implementation:

  • Caller: Lookup context provided by the JVM
  • InvokedName: Invokes the function name, in this case invokedName is “run”
  • SamMethodType: The method signature (parameter type and return value type) defined by the functional interface, in this case the signature “()void” of the run method
  • ImplMethod: The static method corresponding to the lambda expression generated at compile timeinvokestatic LamdaTest.lambda$main$0
  • InstantiatedMethodType: Generally the same as samMethodType or a special case of it, in this case “()void”
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType)
        throws LambdaConversionException {
    AbstractValidatingLambdaMetafactory mf;
    mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                         invokedName, samMethodType,
                                         implMethod, instantiatedMethodType,
                                         false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
    mf.validateMetafactoryArgs();
    return mf.buildCallSite();
}
/ / main calls the InnerClassLambdaMetafactory again
public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
                                   MethodType invokedType,
                                   String samMethodName,
                                   MethodType samMethodType,
                                   MethodHandle implMethod,
                                   MethodType instantiatedMethodType,
                                   booleanisSerializable, Class<? >[] markerInterfaces, MethodType[] additionalBridges)
        throws LambdaConversionException {
    super(caller, invokedType, samMethodName, samMethodType,
          implMethod, instantiatedMethodType,
          isSerializable, markerInterfaces, additionalBridges);
    implMethodClassName = implDefiningClass.getName().replace('. '.'/');
    implMethodName = implInfo.getName();
    implMethodDesc = implMethodType.toMethodDescriptorString();
    implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial)
            ? implDefiningClass
            : implMethodType.returnType();
    constructorType = invokedType.changeReturnType(Void.TYPE);
    lambdaClassName = targetClass.getName().replace('. '.'/') + "$$Lambda$" + counter.incrementAndGet();
    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    int parameterCount = invokedType.parameterCount();
    if (parameterCount > 0) {
        argNames = new String[parameterCount];
        argDescs = new String[parameterCount];
        for (int i = 0; i < parameterCount; i++) {
            argNames[i] = "arg$" + (i + 1); argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i)); }}else{ argNames = argDescs = EMPTY_STRING_ARRAY; }}Copy the code
  • Where lambda expressions are declared, an InvokeDynamic instruction is generated, along with a corresponding Bootstrap Method.
  • Perform invokedynamic instruction for the first time, will call the corresponding guidance Method (the Bootstrap Method), the guidance Method will be called LambdaMetafactory. Metafactory Method dynamically generated inner classes
  • The bootstrap method returns a dynamic CallSite object that calls the inner class that implements the Runnable interface
  • The contents of lambda expressions are compiled into static methods that are called directly by the previously dynamically generated inner class
  • Lambda is actually invoked by InvokeInterface to invoke the method of the inner class generated by CallSite

Third, summary

1. Anonymous inner classes are implemented by generating new class files at compile time
Lambda expressions take the form of not generating classes at compile time, but dynamically generating an inner class at run time