为什么来自类的Java字节码出现了新的staic内部类的代码,出现了jvm指令ACONST_NULL

问题描述

我尝试建立一个内部staic类,但是我发现字节码出现在ACONST_NULLNEWDUP间的jvm指令INVOKE_SPECIAL中, 但是我知道新的一类是

  • DUP
  • INVOKE_SPECIAL
package com.hoho.api;

/**
 * @author linuxea
 */
public class Main {

    private static class InnerMain {
        // no field
    }

    public static void main(String[] args) {
        InnerMain innerMain = new InnerMain();
    }

}

// class version 52.0 (52)
// access flags 0x21
public class com/hoho/api/Main {

  // compiled from: Main.java
  // access flags 0xA
  private static INNERCLASS com/hoho/api/Main$InnerMain com/hoho/api/Main InnerMain
  // access flags 0x1008
  static synthetic INNERCLASS com/hoho/api/Main$1 null null

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 6 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/hoho/api/Main; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
   L0
    LINENUMBER 13 L0
    NEW com/hoho/api/Main$InnerMain
    DUP
    ACONST_NULL
    INVOKESPECIAL com/hoho/api/Main$InnerMain.<init> (Lcom/hoho/api/Main$1;)V
    ASTORE 1
   L1
    LINENUMBER 14 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    LOCALVARIABLE innerMain Lcom/hoho/api/Main$InnerMain; L1 L2 1
    MAXSTACK = 3
    MAXLOCALS = 2
}

为什么

 L0
    LINENUMBER 13 L0
    NEW com/hoho/api/Main$InnerMain
    DUP
    ACONST_NULL
    INVOKESPECIAL com/hoho/api/Main$InnerMain.<init> (Lcom/hoho/api/Main$1;)V
    ASTORE 1

ACONST_NULL是什么?

解决方法

这是javac解决JDK 11之前的私有成员访问问题的方式。

InnerMain被声明为私有,并且具有私有默认构造函数:

private InnerMain() {
}

根据JVM规范,任何类都不能访问其他类的私有成员,但是根据Java语言规则,Main应该能够访问InnerMain的私有成员。为了解决这个问题,javac生成了一个综合包专用桥构造函数。为了确保不直接从用户代码中调用此构造函数,编译器会在签名中添加一个伪类Main$1

// Real constructor
private InnerMain() {
}


// Synthetic bridge constructor (package private,so Main can call it)
InnerMain(Main$1 dummy) {
    this();
}

编写new InnerMain()时,编译器实际上会调用此桥构造函数。虚拟参数的实际值无关紧要,因此将其设置为null-因此设置为ACONST_NULL指令:

public static void main(String[] args) {
    InnerMain innerMain = new InnerMain(null);
}

请注意,当内部类是public或package private时,不需要桥接方法。

自JDK 11开始,引入了一种全新的机制,请参见JEP 181: Nest-Based Access Control。现在,如果您使用JDK 11或更高版本编译Main类,则MainInnerMain将成为嵌套对象,并且无需桥接方法和合成类即可访问彼此的私有成员。