JVM en profundidad: ejemplos detallados de códigos de operación relacionados con la invocación

Hay un capítulo en la Especificación de la máquina virtual Java que enumera específicamente los mnemónicos del código de operación, y el enlace correspondiente es: Especificación de la máquina virtual de Java: Capítulo 7. Mnemónicos del código de operación por código de operación

Entre ellos, los códigos de operación relacionados con las llamadas a métodos son:

decimal hexadecimal mnemotécnico ilustrar
182 (0xb6) invocarvirtual Invocar un método de instancia de una clase;
183 (0xb7) invocar especial Llamar a métodos de instancia especiales, como constructores, métodos de superclase y privados.
184 (0xb8) invocadorestático llamar al método estático
185 (0xb9) invocar interfaz método de interfaz de llamada
186 (0xba) invocar dinámica llamada a método dinámico

Presentémoslo en detalle a través de ejemplos prácticos.

Por favor vea el código:

package com.cncounter.opcode;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 演示invoke操作码
 */
public class DemoInvokeOpcode {
    
    

  public static void testMethodInvoke() {
    
    
      // 183; invokespecial
      HashMap<String, String> hashMap = new HashMap<String, String>(100);
      // 182; invokevirtual
      hashMap.put("name", "tiemao");
      // 赋值给Map接口引用
      Map<String, String> map = hashMap;
      // 185; invokeinterface
      map.putIfAbsent("url", "https://renfufei.blog.csdn.net");
      // 使用lambda
      List<String> upperKeys = map.keySet().stream()
              // 186; invokedynamic
              .map(i -> i.toUpperCase())
              .collect(Collectors.toList());
      // 184; invokestatic
      String str = String.valueOf(upperKeys);
      // 182; invokevirtual
      System.out.println(str);
  }

  public static void main(String[] args) {
    
    
      // 184; invokestatic
      testMethodInvoke();
  }
}

El resultado después de ejecutar el método principal es:

[NAME, URL]

Podemos compilar y descompilar con los siguientes comandos:

# 查看JDK工具的帮助信息
javac -help
javap -help

# 带调试信息编译
javac -g DemoInvokeOpcode.java
# 反编译
javap -v DemoInvokeOpcode.class

# 因为带了package, 所以执行时需要注意路径:
cd ../../..
java com.cncounter.opcode.DemoInvokeOpcode

Después de compilar javac, puede ver que solo se genera un archivo DemoInvokeOpcode.class. Aquí es donde las lambdas se diferencian de las clases internas.

La información de código de bytes generada por la herramienta de descompilación javap es mucha, y se extrae la parte del método testMethodInvoke que más nos preocupa:

public static void testMethodInvoke();
  descriptor: ()V
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
    stack=3, locals=4, args_size=0
       0: new           #2                  // class java/util/HashMap
       3: dup
       4: bipush        100
       6: invokespecial #3                  // Method java/util/HashMap."<init>":(I)V
       9: astore_0
      10: aload_0
      11: ldc           #4                  // String name
      13: ldc           #5                  // String tiemao
      15: invokevirtual #6                  // Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      18: pop
      19: aload_0
      20: astore_1
      21: aload_1
      22: ldc           #7                  // String url
      24: ldc           #8                  // String https://renfufei.blog.csdn.net
      26: invokeinterface #9, 3            // InterfaceMethod java/util/Map.putIfAbsent:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      31: pop
      32: aload_1
      33: invokeinterface #10, 1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
      38: invokeinterface #11, 1           // InterfaceMethod java/util/Set.stream:()Ljava/util/stream/Stream;
      43: invokedynamic #12, 0             // InvokeDynamic #0:apply:()Ljava/util/function/Function;
      48: invokeinterface #13, 2           // InterfaceMethod java/util/stream/Stream.map:(Ljava/util/function/Function;)Ljava/util/stream/Stream;
      53: invokestatic  #14                 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
      56: invokeinterface #15, 2           // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
      61: checkcast     #16                 // class java/util/List
      64: astore_2
      65: aload_2
      66: invokestatic  #17                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      69: astore_3
      70: getstatic     #18                 // Field java/lang/System.out:Ljava/io/PrintStream;
      73: aload_3
      74: invokevirtual #19                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      77: return
    LineNumberTable:
      line 15: 0
      line 17: 10
      line 19: 19
      line 21: 21
      line 23: 32
      line 25: 48
      line 26: 53
      line 28: 65
      line 30: 70
      line 31: 77
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
         10      68     0 hashMap   Ljava/util/HashMap;
         21      57     1   map   Ljava/util/Map;
         65      13     2 upperKeys   Ljava/util/List;
         70       8     3   str   Ljava/lang/String;
    LocalVariableTypeTable:
      Start  Length  Slot  Name   Signature
         10      68     0 hashMap   Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;
         21      57     1   map   Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;
         65      13     2 upperKeys   Ljava/util/List<Ljava/lang/String;>;

Una explicación sencilla es la siguiente:

  • Para llamar a un método estático de una clase, se utiliza la instrucción invokestatic.
  • Al llamar a un método a través de una referencia de interfaz, se compilará directamente en la instrucción invokeinterface.
  • La llamada a un constructor se compilará en una instrucción especial de invocación, que por supuesto también incluye la llamada a un método privado, así como a un método de superclase visible.
  • Si el tipo al que hace referencia la variable es una clase concreta, el compilador usa invokevirtual para invocar métodos en los niveles público, protegido y visible del paquete.
  • JDK7 ha agregado una nueva invokedynamicinstrucción para admitir el "lenguaje de tipo dinámico" (Dynamically TypedLanguage, la expresión lambda introducida desde JDK8, que se compilará en esta instrucción cuando se use.

Para obtener más artículos, consulte el proyecto de traducción de artículos en GitHub: https://github.com/cncounter/translation

Al mismo tiempo, ¡a todos, por favor, dale me gusta a Star para apoyar!

Enlace original: Artículo de 2020: 41. JVM en profundidad: ejemplos detallados de códigos de operación relacionados con la invocación

Supongo que te gusta

Origin blog.csdn.net/renfufei/article/details/112463313
Recomendado
Clasificación