"In-depth understanding of Java virtual machine" How does JVM realize the dynamic invocation of methods? method handle

"In-depth understanding of Java virtual machine" How does JVM realize the dynamic invocation of methods? method handle

1. The reason for the appearance of the method handle

There is a running race in a certain country, with Asians, Europeans or Africans participating, but robots also participate. Robots do not belong to the human camp, so how can robots join in?


interface Human{
    void race();
}

class Asian implements Human{

   public void race(){
       System.out.println("asian running");
   } ;

}

class European implements Human{

    public void race(){
        System.out.println("european running");
    }

}

class African implements Human{
    public void race(){
        System.out.println("african running");
    }
}

class Robot{

    public void race(){
        System.out.println("robot running");
    }
}

Usually our approach is to create a wrapper class to wrap the Robot's race method, as follows:

class SpecialHuman implements Human{

    private Robot robot;


    @Override
    public void race() {
        robot.race();
    }
}

Or you can use reflection to call the race method in each class

Method 1: call method

 //方式一:包装
        Asian asian = new Asian();
        European european = new European();
        African african = new African();

        //参赛选手集合
        ArrayList<Human> humans = new ArrayList<>();
        humans.add(african);
        humans.add(european);
        humans.add(asian);
        Robot robot = new Robot();
        SpecialHuman specialHuman = new SpecialHuman();
        specialHuman.setRobot(robot);
        humans.add(specialHuman);

        for (Human human : humans) {
            human.race();
        }

Method 2: reflection calling method

 //方法二
        Asian asian = new Asian();
        European european = new European();
        African african = new African();
        Robot robot = new Robot();

        ArrayList<Object> objectArrayList = new ArrayList<>();

        objectArrayList.add(african);
        objectArrayList.add(european);
        objectArrayList.add(asian);
        objectArrayList.add(robot);

        for (Object o : objectArrayList) {
            Class<?> aClass = o.getClass();
            Method race = aClass.getMethod("race");
            race.invoke(o);
        }

Both methods are more complicated and less efficient than direct calls. In order to solve this problem, starting from Java7, invokedynamic is introduced to dynamically call methods. The call mechanism of this instruction abstracts the concept of call point, and allows the application to link the call point to any eligible method.

As a preparation for invokedynamic, Java 7 introduces a lower-level, more flexible method abstraction: method handle (MethodHandle).

2. What is a method handle

A method handle is a strongly typed reference that can be executed directly [2]. The reference can point to a regular static method or instance method, or to a constructor or field. When pointing to a field, the method handle actually points to a fictitious method containing the field access bytecode, which is semantically equivalent to the getter or setter method of the target field. Note that it is semantically, not actually equal, and may actually be the getter of a field The method is not to get the value of the field itself.

Method handles are matched by the method's parameter list and return type, regardless of the method name and class name .

The access rights of the method handle are determined by where the LookUp is created, regardless of where the handle is created. Execution is not restricted by permission modifiers. In the following code, LookUp is created in a static public method, so the Foo class is equivalent to streaking, and the bar method can be called anytime and anywhere without turning on violent reflection

class Foo {
  private static void bar(Object o) {
    ..
  }
  public static Lookup lookup() {
    return MethodHandles.lookup();
  }
}

// 获取方法句柄的不同方式
MethodHandles.Lookup l = Foo.lookup(); // 具备Foo类的访问权限
Method m = Foo.class.getDeclaredMethod("bar", Object.class);
MethodHandle mh0 = l.unreflect(m);

MethodType t = MethodType.methodType(void.class, Object.class);
MethodHandle mh1 = l.findStatic(Foo.class, "bar", t);

image-20230514111432735

3. Operation of method handle

handle call

invokeExact that strictly matches the parameter type

Method handle calls can be divided into two types, one is invokeExact that needs to strictly match the parameter type. How strict is it? Assuming that a method handle will receive a parameter of type Object, if you directly pass in a String as an actual parameter, then the invocation of the method handle will throw an exception that the method type does not match at runtime. The correct calling method is to explicitly convert the String to Object type.

For example, in the above code, if we change invoke to the invokeExact method, an error will be reported when the execution is executed, and it will tell you that the expected type of the parameter is inconsistent


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

class Foo {
        private static void bar(Object o) {
            System.out.println("bar 执行了");
        }
        public static MethodHandles.Lookup lookup() {
            return MethodHandles.lookup();
        }
    }

class Main2{

    public static void main(String[] args) throws Throwable {
        //获取方法对象
        Class<Foo> fooClass = Foo.class;
        Method bar = fooClass.getDeclaredMethod("bar", Object.class);

        //获取方法句柄
        MethodHandles.Lookup lookup = Foo.lookup();
        MethodHandle unreflect = lookup.unreflect(bar);
        unreflect.invokeExact(new String());
    }

}

image-20230514135756045

Follow up the method and you will find that there is a @PolymorphicSignature annotation on the method declaration of this local method. When encountering a method call annotated by it, the Java compiler will generate a method descriptor based on the declared type of the passed parameter, instead of using the descriptor declared by the target method.

image-20230514135844876

The bytecode of the following code is shown in


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

class Foo {
        private static void bar(Object o) {
            System.out.println("bar 执行了");
        }
        public static MethodHandles.Lookup lookup() {
            return MethodHandles.lookup();
        }
    }

class Main2{

    public static void main(String[] args) throws Throwable {
        //获取方法对象
        Class<Foo> fooClass = Foo.class;
        Method bar = fooClass.getDeclaredMethod("bar", Object.class);

        //获取方法句柄
        MethodHandles.Lookup lookup = Foo.lookup();
        MethodHandle unreflect = lookup.unreflect(bar);
        unreflect.invokeExact(new String());
        unreflect.invokeExact(new String());
    }

}

 0 ldc #2 <Foo>
 2 astore_1
 3 aload_1
 4 ldc #3 <bar>
 6 iconst_1
 7 anewarray #4 <java/lang/Class>
10 dup
11 iconst_0
12 ldc #5 <java/lang/Object>
14 aastore
15 invokevirtual #6 <java/lang/Class.getDeclaredMethod : (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;>
18 astore_2
19 invokestatic #7 <Foo.lookup : ()Ljava/lang/invoke/MethodHandles$Lookup;>
22 astore_3
23 aload_3
24 aload_2
25 invokevirtual #8 <java/lang/invoke/MethodHandles$Lookup.unreflect : (Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;>
28 astore 4
30 aload 4
32 new #9 <java/lang/String>
35 dup
36 invokespecial #10 <java/lang/String.<init> : ()V>
39 invokevirtual #11 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/String;)V>
42 aload 4
44 new #5 <java/lang/Object>
47 dup
48 invokespecial #1 <java/lang/Object.<init> : ()V>
51 invokevirtual #12 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/Object;)V>
54 return

image-20230514144257005

At the same time, we can also find that the instruction called by the method handle is invokevirtual. In fact, this instruction is consistent with the original method call instruction. Here, the actual method is called, so it is invokevirtual

Invoke that automatically adapts the parameter type

If you need to automatically adapt the parameter type, then you can choose the second calling method invoke of the method handle. It is also a method of signature polymorphism. invoke will call the MethodHandle.asType method to generate an adapter method handle, adapt the incoming parameters, and then call the original method handle. The return value of calling the original method handle will also be adapted first, and then returned to the caller.

4. The method handle calls the target method

Rewrite the first race run code

import jdk.nashorn.internal.runtime.Specialization;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Objects;

interface Human {
    void race();
}

class Asian implements Human {

    public void race() {
        System.out.println("asian running");
    };

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }

}

class European implements Human {

    public void race() {
        System.out.println("european running");
    }

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class African implements Human {
    public void race() {
        System.out.println("african running");
    }
    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class Robot {

    public void race() {
        System.out.println("robot running");
    }

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class SpecialHuman implements Human {

    private Robot robot;

    public Robot getRobot() {
        return robot;
    }

    public void setRobot(Robot robot) {
        this.robot = robot;
    }

    @Override
    public void race() {
        robot.race();
    }
}

public class RunningRace {

    public static void main(String[] args) throws Throwable {

            //拿到所有的Lookup,创建方法句柄
        MethodHandles.Lookup lookup1 = Asian.lookup();
        MethodHandles.Lookup lookup2 = European.lookup();
        MethodHandles.Lookup lookup3 = African.lookup();
        MethodHandles.Lookup lookup4 = Robot.lookup();

        Asian asian = new Asian();
        European european = new European();
        African african = new African();
        Robot robot = new Robot();

         lookup1.findVirtual(Asian.class, "race", MethodType.methodType(void.class)).invoke(asian);
        lookup2.findVirtual(European.class,"race", MethodType.methodType(void.class)).invoke(european);
        lookup3.findVirtual(African.class,"race", MethodType.methodType(void.class)).invoke(african);
        lookup4.findVirtual(Robot.class,"race", MethodType.methodType(void.class)).invoke(robot);

    }

}

Guess you like

Origin blog.csdn.net/JAVAlife2021/article/details/130669469