JVM角度看方法调用-MethodHandle篇

MethodHandle入门 java.lang.invoke包中的MethodHandle API是一个功能强大的反射和代码生成 API,并且还具有广泛的 JIT 支持。在这篇博文中,我将概述此 API。我将介绍 API 的一些最常用部分,但这不是一份全面的指南。它旨在为您提供一个起点,让您可以开始自行学习更多内容。鼓励读者自己尝试这些示例。 阅读详情


JVM角度看方法调用系列文章:

1.JVM角度看方法调用-开篇

2.JVM角度看方法调用-反射篇

3.JVM角度看方法调用-MethodHandle篇

4.JVM角度看方法调用-性能压测篇(码字…)

5.JVM角度看方法调用-Lambda篇(码字…)

​ 在我们平时开发中常用的方法调用有三种:直接调用、反射调用、MethodHandle调用,这一系列文章就围绕着三种调用方式进行原理剖析和性能分析,本文JDK的版本是1.8。


在上篇文章中我们最后总结由于反射调用的种种缺陷严重影响性能,所以当我们热点代码需要用到反射调用时不妨尝试用MethodHandle代替。同时也用MethodHandle调用与优化到极致的反射调用进行进行了比较,结果是MethodHandle完爆反射,性能和直接调用不相上下。要想弄清MethodHandle强大的原因要结合HotSpot源码进行分析,只在java代码中是没办法找到真正原因的。

先抛几个问题,在后文慢慢解答:

  1. 为什么MethodHandle.invoke()不用进行方法修饰符权限检查?反射表示不服
  2. 为什么MethodHandle声明时要被final修饰,否则性能会大大打折扣?
  3. 为什么MethodHandle.invoke()明明是native方法为什么还可以被JIT内联优化?反射表示不服
  4. MethodHandle.invoke()方法的调用栈链路是什么?

接下来我们一点点将它“脱干净”,看看它牛逼在哪里😏~

一、MethodHandle是什么

​ 方法句柄(MethodHandle)是一个强类型的,能够被直接执行的引用。从作用上来看,方法句柄类似于反射中的Method类,是对要执行的方法的一个引用,可以简单理解为函数指针,它是一种更加底层的查找、调整和调用方法的机制。该引用可以指向常规的静态方法或者实例方法,也可以指向构造器或者字段。它调用时有两个方法 invoke和invokeExact,后者要求参数类型与底层方法的参数完全匹配,前者则在有出入时做修改如包装类型。

​ MethodHandle是在JDK7中新加入的,与其相关的还有MethodType和MethodHandles.Lookup两个核心类。方法句柄的类型(MethodType)是由所指向方法的参数类型以及返回类型组成的。它是用来确认方法句柄是否适配的唯一关键;MethodHandles.Lookup则是用来创建方法句柄的,它提供了多个 API,既可以使用反射 API 中的 Method 来查找,也可以根据类、方法名以及方法句柄类型来查找。

​ 下面举一个MethodHandle创建及方法修饰符权限检查的例子:

	public class Animal {
    /**
     * 小动物可以进行简单的加法运算
     * @param one
     * @param two
     * @return
     */
    private int calculation(int one,int two){
        return (one+two);
    }

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



public class MethodHandleDemo {

    public static void main(String[] args)throws Throwable{
        // 1.创建MethodHandles.Lookup
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        // 2.指定 返回值类型 和 参数类型 ,定义目标方法的MethodType
        MethodType methodType=MethodType.methodType(int.class, new Class[]{int.class, int.class});
        // 3.通过MethodHandles.Lookup指定方法定义类、方法名称以及MethodType 来查找对应的方法句柄
        MethodHandle methodHandle = lookup.findSpecial(Animal.class,"calculation",methodType,Animal.class).bindTo(new Animal());
        // 4.利用方法句柄进行方法调用
        int result = (int) methodHandle.invoke(2, 3);
        System.out.println(result);
    }
}

上面例子中想要在MethodHandleDemo中利用MethodHandle调用Animal中的calculation(int one,int two)私有方法。上面例子运行后会在步骤3获取MethodHandle时抛出外部类MethodHandle无法访问Animal类中私有方法句柄的异常。注意哈这个异常是在获取方法句柄时抛出的,而不是方法句柄执行时抛出的。

Exception in thread "main" java.lang.IllegalAccessException: no private access for invokespecial: class jvm.jit.Animal, from jvm.jit.MethodHandleDemo
	at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
	at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)
	at java.lang.invoke.MethodHandles$Lookup.findSpecial(MethodHandles.java:1002)
	at jvm.jit.MethodHandleDemo.main(MethodHandleDemo.java:16)

现在我们修改下MethodHandleDemo中MethodHandles.Lookup的获取方式,修改为调用Animal中的getLookup()方法获取。

public class MethodHandleDemo {

    public static void main(String[] args)throws Throwable{
        // 修改MethodHandles.Lookup的获取方式
        MethodHandles.Lookup lookup=Animal.getLookup();
       
        MethodType methodType=MethodType.methodType(int.class, new Class[]{int.class, int.class});
        MethodHandle methodHandle = lookup.findSpecial(Animal.class,"calculation",methodType,Animal.class).bindTo(new Animal());
        int result = (int) methodHandle.invoke(2, 3);
        System.out.println(result);
    }
}

此时在执行则可以正常输出result=5。上例中我们仅仅修改了MethodHandles.Lookup的创建位置就可以在外部类中调用私有方法了说明方法句柄的访问权限不取决于方法句柄的创建位置,而是取决于 Lookup 对象的创建位置。

​ 句柄在设计的时候就将方法修饰符权限检查放在了通过 MethodHandles.Lookup获取MethodHandle的时候,MethodHandle的调用不会进行权限检查。如果该句柄被多次调用的话,那么与反射调用相比,它无需重复权限检查的开销。

​ 至此简单介绍了什么是方法句柄(MethodHandle),同时解答了文章开头抛出的第一个问题1.为什么MethodHandle.invoke()不用进行方法修饰符权限检查?。本文重在MethodHandle执行方法调用的原理剖析,所以就不过多介绍MethodHandle相关的API使用,想了解的可以看下官方文档。

二、MethodHandle调用的原理

2.1、动态签名

​ MethodHandle类中声明 invoke和invokeExact两个负责句柄调用执行的native方法。他们的执行原理是一样的只是invokeExact对入参和返回值的要求严格一些。下面我们就用invoke来完成MethodHandle调用的原理的讲解。首先看个例子:

public class MethodHandleDemo {

    public static void main(String[] args)throws Throwable{
        MethodHandles.Lookup lookup=Animal.getLookup();
        MethodType methodType=MethodType.methodType(int.class, new Class[]{int.class, int.class});
        MethodHandle methodHandle = lookup.findSpecial(Animal.class,"calculation",methodType,Animal.class).bindTo(new Animal());

        int result = (int) methodHandle.invoke(2, 3);

        String result1 = (String) methodHandle.invoke("2", 3);

        Float result2 = (Float) methodHandle.invoke(2L, (Double)3.1415926,new Animal());
    }
}

main对应的字节码如下,我删除无用部分,只保留最后三个methodHandle.invoke相关的字节码。

...
  //int result = (int) methodHandle.invoke(2, 3);
52 invokevirtual #11 <java/lang/invoke/MethodHandle.invoke : (II)I>
...
  //String result1 = (String) methodHandle.invoke("2", 3);
61 invokevirtual #13 <java/lang/invoke/MethodHandle.invoke : (Ljava/lang/String;I)Ljava/lang/String;>
...
  // Float result2 = (Float) methodHandle.invoke(2L, (Double)3.1415926,new Animal());
83 invokevirtual #19 <java/lang/invoke/MethodHandle.invoke : (JLjava/lang/Double;Ljvm/jit/Animal;)Ljava/lang/Float;>
...

由此可以发现javac编译器在编译invoke方法时与编译普通invokevirtual指令(虚方法)是有所不同的,不同的是符号类型描述符是从实际的参数和返回类型派生的,而不是从方法声明派生的。javac编译器根据方法是否带有@PolymorphicSignature注解来判断是否进行多态签名处理。在MethodHandle的java doc里面有相关注释。

在这里插入图片描述

当然在invoke的后续执行中会字节码的动态签名与MethodHandle中的MethodType进行比较如果不一致会抛出异常,上例中异常如下:

Exception in thread "main" java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(int,int)int to (String,int)String
	at java.lang.invoke.MethodHandle.asTypeUncached(MethodHandle.java:775)
	at java.lang.invoke.MethodHandle.asType(MethodHandle.java:761)
	at java.lang.invoke.Invokers.checkGenericType(Invokers.java:321)
	at jvm.jit.MethodHandleDemo.main(MethodHandleDemo.java:17)

2.2、指令重写

​ 先说结论:在类加载时期,jvm会扫描类中定义的全部方法,对方法的字节码进行优化(重排序、重写等),其中会将调用MethodHandle.invoke方法的invokevirtual指令重写为invokehandle指令。所以MethodHandle.invoke方法的调用并不是利用JNI机制来进行native方法调用而是执行invokehandle指令。

​ 上面讲解了javac对invoke方法编译时做了动态签名处理,这为后续的方法签名类型检查以及**方法分派(MethodHandleNatives.linkMethod方法生成MemberName)**奠定了基础。接下来我们看下invoke方法的java调用链路,首先在方法中制造一个 1/0的异常,再添加-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames虚拟机参数来打印隐藏的栈信息。

java.lang.ArithmeticException: / by zero
	at jvm.jit.Animal.calculation(Animal.java:22)
	at java.lang.invoke.LambdaForm$DMH006/654582261.invokeSpecial_000_LII_I(LambdaForm$DMH006:1000014)
	at java.lang.invoke.LambdaForm$BMH001/985934102.reinvoke_001(LambdaForm$BMH001:1000024)
	at java.lang.invoke.LambdaForm$MH012/1654589030.invoke_000_MT(LambdaForm$MH012:1000022)
	at jvm.jit.MethodHandleDemo.main(MethodHandleDemo.java:15)

可以看到调用栈由MethodHandle.invoke直接跳转到一个动态生成的类LambdaForm$MH中。java中的调用栈链路明确了以后,我们来啃下硬骨头看下native方法invoke的实现。在HotSpot源码中找到了MethodHandle.invoke相关的实现,采用的是JNI动态注册的方式:

在这里插入图片描述

/**
 * Throws a java/lang/UnsupportedOperationException unconditionally.
 * This is required by the specification of MethodHandle.invoke if
 * invoked directly.
 */
JVM_ENTRY(jobject, MH_invoke_UOE(JNIEnv* env, jobject mh, jobjectArray args)) {
  THROW_MSG_NULL(vmSymbols::java_lang_UnsupportedOperationException(), "MethodHandle.invoke cannot be invoked reflectively");
  return NULL;
}
JVM_END

What???MethodHandle.invoke的实现竟然是啥也没干直接抛出一个异常java.lang.UnsupportedOperationException,第一次看到我人傻了一下没思路了。

但是通过方法注释和异常描述看出了端倪:

方法注释:This is required by the specification of MethodHandle.invoke if invoked directly.

MethodHandle机制规定MethodHandle.invoke方法只能够被直接调用。

异常描述:MethodHandle.invoke cannot be invoked reflectively

MethodHandle.invoke方法不能被反射调用

​ 也就是说只有反射调用MethodHandle.invoke方法时才会走这个实现,直接调用走其他实现。因为之前阅读过HotSpot类加载步骤相关的源码,在类加载到JVM中后会对.class文件中的字节码进行优化,其中就包含指令的重新。立即就想到了这个可能性,invokevirtual <java/lang/invoke/MethodHandle.invoke>这个指令会不会被此时被重写了 ,接下来看下相关源码。.class文件的类加载方法在java中的入口是Classloader.loadClass(),一步步调用只JVM的源码中,具体调用栈细节可以看类加载时JVM在搞什么?JVM源码分析+OOP-KLASS模型分析,我们这里只看和指令重写相关的源码调用栈。

SystemDictionary::parse_stream方法主要是解析.class文件的数据流文件,创建初始化instanceKlass

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/systemDictionary.cpp
  
Klass* SystemDictionary::parse_stream(Symbol* class_name,
                                      Handle class_loader,
                                      Handle protection_domain,
                                      ClassFileStream* st,
                                      KlassHandle host_klass,
                                      GrowableArray<Handle>* cp_patches,
                                      TRAPS) {
	//...忽略部分代码

  // 获取加载.class文件保存在方法区的元数据instanceKlass
  instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
                                                             loader_data,
                                                             protection_domain,
                                                             host_klass,
                                                             cp_patches,
                                                             parsed_name,
                                                             true,
                                                             THREAD);

    // 类链接:
    // 1.指令重写,将特定的字节码进行指令重新,如MethodHandle.invoke方法调用时原字节码中为invokevirtual指令,在这一步会被重写为invokehandle
    // 2.方法链接,为方法编译出解释器和编译器两种入口,ClassFileParser(st).parseClassFile中会根据.class文件中方法相关的字节码创建方法的局部变量表空间,操作数栈空间等,但此时方法仍然不可以被调用执行,必须经过方法链接才算完整。解释器和编译器两种入口即方法的指针。
    // 3.初始化vtable和itable,vtable和itable的创建和大小计算在ClassFileParser(st).parseClassFile中已完成,在此处填充引用。
    k->link_class(CHECK_NULL);
  
    if (cp_patches != NULL) {
      // 将常量池中Utf8、String、int、long等常量的符号引用解析成直接引用,像Methodref、Fieldref等类型的符号引用在运行时首次调用时才会解析成直接引用,并触发相关定义类的类加载(解析阶段并不是只发生类加载期间,在方法调用期间也会存在解析操作)。
      k->constants()->patch_resolved_references(cp_patches);
    }
    // 类初始化入口,包括对<cinit>方法的调用,过程加锁
    k->eager_initialize(CHECK_NULL);

		//...忽略部分代码
  return k();
}

上述代码可知在k->link_class(CHECK_NULL)方法中完成了指令的重写。

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/oops/instanceKlass.cpp
  
void InstanceKlass::link_class(TRAPS) {
  assert(is_loaded(), "must be loaded");
  if (!is_linked()) {
    HandleMark hm(THREAD);
    instanceKlassHandle this_oop(THREAD, this);
    link_class_impl(this_oop, true, CHECK);
  }
}


bool InstanceKlass::link_class_impl(
    instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
	//...忽略部分代码
        // also sets rewritten
        // 字节码指令重写
        this_oop->rewrite_class(CHECK_false);
	//...忽略部分代码
  return true;
}


void InstanceKlass::rewrite_class(TRAPS) {
  assert(is_loaded(), "must be loaded");
  instanceKlassHandle this_oop(THREAD, this);
  if (this_oop->is_rewritten()) {
    assert(this_oop()->is_shared(), "rewriting an unshared class?");
    return;
  }
  Rewriter::rewrite(this_oop, CHECK);
  this_oop->set_rewritten();
}

然后看下Rewriter::rewrite()方法

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/interpreter/rewriter.cpp
// 对.class文件进行指令重写,比如MethodHandle.java中被标记@PolymorphicSignature的方法
void Rewriter::rewrite(instanceKlassHandle klass, TRAPS) {
  ResourceMark rm(THREAD);
  Rewriter     rw(klass, klass->constants(), klass->methods(), CHECK);
  // (That's all, folks.)
}

Rewriter::Rewriter(instanceKlassHandle klass, constantPoolHandle cpool, Array<Method*>* methods, TRAPS)
  : _klass(klass),
    _pool(cpool),
    _methods(methods)
{
	//...忽略部分代码
  for (int i = len-1; i >= 0; i--) {
    Method* method = _methods->at(i);
    // 遍历全部方法,针对每个方法进行扫描,进行指令重写
    scan_method(method, false, &invokespecial_error);
    if (invokespecial_error) {
      THROW_MSG(vmSymbols::java_lang_InternalError(),
                "This classfile overflows invokespecial for interfaces "
                "and cannot be loaded");
      return;
     }
  }
	//...忽略部分代码
}




void Rewriter::scan_method(Method* method, bool reverse, bool* invokespecial_error) {

			//...忽略部分代码
      switch (c) {
				//...忽略部分代码
        case Bytecodes::_getstatic      : 
        case Bytecodes::_putstatic      : 
        case Bytecodes::_getfield       : 
        case Bytecodes::_putfield       : 
        case Bytecodes::_invokevirtual  : 
        case Bytecodes::_invokestatic   :
        case Bytecodes::_invokeinterface:
        case Bytecodes::_invokehandle   : 
          // 此处是针对invokevirtual指令的重写
          rewrite_member_reference(bcp, prefix_length+1, reverse);
          break;
        case Bytecodes::_invokedynamic:
          rewrite_invokedynamic(bcp, prefix_length+1, reverse);
          break;
          	//...忽略部分代码
      }
    }
  }
		//...忽略部分代码
}


void Rewriter::rewrite_member_reference(address bcp, int offset, bool reverse) {
  
  if (!reverse) {
    int  cp_index    = Bytes::get_Java_u2(p);
    int  cache_index = cp_entry_to_cp_cache(cp_index);
    Bytes::put_native_u2(p, cache_index);
    if (!_method_handle_invokers.is_empty())
      // invokevirtual指令如果调用的是MethodHandle.invoke系列的接口则尝试重写
      maybe_rewrite_invokehandle(p - 1, cp_index, cache_index, reverse);
  } else {
    int cache_index = Bytes::get_native_u2(p);
    int pool_index = cp_cache_entry_pool_index(cache_index);
    Bytes::put_Java_u2(p, pool_index);
    if (!_method_handle_invokers.is_empty())
      maybe_rewrite_invokehandle(p - 1, pool_index, cache_index, reverse);
  }
}


// Adjust the invocation bytecode for a signature-polymorphic method (MethodHandle.invoke, etc.)
void Rewriter::maybe_rewrite_invokehandle(address opc, int cp_index, int cache_index, bool reverse) {
  if (!reverse) {
    if ((*opc) == (u1)Bytecodes::_invokevirtual ||
        // allow invokespecial as an alias, although it would be very odd:
        (*opc) == (u1)Bytecodes::_invokespecial) {
      //...忽略部分代码
      if (status > 0) {
        // 在此处将符合条件的invokevirtual指令重写为invokehandle指令
        (*opc) = (u1)Bytecodes::_invokehandle;
      }
    }
  } else {
    // Do not need to look at cp_index.
    if ((*opc) == (u1)Bytecodes::_invokehandle) {
      (*opc) = (u1)Bytecodes::_invokevirtual;
      // Ignore corner case of original _invokespecial instruction.
      // This is safe because (a) the signature polymorphic method was final, and
      // (b) the implementation of MethodHandle will not call invokespecial on it.
    }
  }
}

至此我们在源码得到了验证, 在类加载时期,jvm会扫描类中定义的全部方法,对方法的字节码进行优化(重排序、重写等),其中会将调用MethodHandle.invoke方法的invokevirtual指令重写为invokehandle指令。所以MethodHandle.invoke方法的调用并不是利用JNI机制来进行native方法调用而是执行invokehandle指令。

​ 这个结论同时解答了文章开头抛出的第三个问题3.为什么MethodHandle.invoke()明明是native方法为什么还可以被JIT内联优化?。

这里再回忆下native方法不能被内联的原因:

​ 一段代码JIT是否会进行优化是通过解释器执行字节码指令时收集的profile(运行时信息)所决定的。而native方法的方法体没有字节码指令,是直接通过本地方法栈执行的。所以native方法不能JIT优化不能被内联到java方法的调用侧。在Hotspot虚拟机的JIT编译器设计的时候就是针对字节码指令进行优化的。

​ 而MethodHandle.invoke()方法的调用并不是利用JNI机制来进行native方法调用而是执行invokehandle指令,所以在解释器解释执行invokehandle字节码指令时可以收集的profile(运行时信息),在运行一段时间后JIT可以根据收集的profile信息对其进行内联优化。整个调用过程始终在java方法栈中运行,没有用到本地方法栈。

2.3、invokehandle指令

​ 接下来我们看下invokehandle指令主要是干什么的,是怎样从MethodHandle.invoke()方法调用到java.lang.invoke.LambdaForm$MH/140435067.invoke_MT()中的。我们看下在HotSpot源码中解释器是如何解释invokehandle指令的。

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp      
	CASE(_invokehandle): {

        if (!EnableInvokeDynamic) {
          ShouldNotReachHere();
        }

        u2 index = Bytes::get_native_u2(pc+1);
        ConstantPoolCacheEntry* cache = cp->entry_at(index);
				// 如果这个指令在常量池中没有直接引语缓存,则对其进行解析,将符号引用转换成直接引用
        if (! cache->is_resolved((Bytecodes::Code) opcode)) {
          CALL_VM(InterpreterRuntime::resolve_invokehandle(THREAD),
                  handle_exception);
          cache = cp->entry_at(index);
        }

        Method* method = cache->f1_as_method();
        if (VerifyOops) method->verify();

        if (cache->has_appendix()) {
          ConstantPool* constants = METHOD->constants();
          SET_STACK_OBJECT(cache->appendix_if_resolved(constants), 0);
          MORE_STACK(1);
        }

        istate->set_msg(call_method);
        istate->set_callee(method);
        istate->set_callee_entry_point(method->from_interpreted_entry());
        istate->set_bcp_advance(3);

        UPDATE_PC_AND_RETURN(0); 
      }

看下invokehandle指令的解析过程

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
  
IRT_ENTRY(void, InterpreterRuntime::resolve_invokehandle(JavaThread* thread)) {
  assert(EnableInvokeDynamic, "");
  const Bytecodes::Code bytecode = Bytecodes::_invokehandle;

  // resolve method
  CallInfo info;
  constantPoolHandle pool(thread, method(thread)->constants());

  {
    JvmtiHideSingleStepping jhss(thread);
    // 解析核心方法
    LinkResolver::resolve_invoke(info, Handle(), pool,
                                 get_index_u2_cpcache(thread, bytecode), bytecode, CHECK);
  } // end JvmtiHideSingleStepping

  cache_entry(thread)->set_method_handle(pool, info);
}
IRT_END
  

看下 LinkResolver::resolve_invoke()

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/interpreter/linkResolver.cpp
  
void LinkResolver::resolve_invoke(CallInfo& result, Handle recv, constantPoolHandle pool, int index, Bytecodes::Code byte, TRAPS) {
  switch (byte) {
    case Bytecodes::_invokestatic   : resolve_invokestatic   (result,       pool, index, CHECK); break;
    case Bytecodes::_invokespecial  : resolve_invokespecial  (result,       pool, index, CHECK); break;
    case Bytecodes::_invokevirtual  : resolve_invokevirtual  (result, recv, pool, index, CHECK); break;
      // invokehandle指令相关解析
    case Bytecodes::_invokehandle   : resolve_invokehandle   (result,       pool, index, CHECK); break;
    case Bytecodes::_invokedynamic  : resolve_invokedynamic  (result,       pool, index, CHECK); break;
    case Bytecodes::_invokeinterface: resolve_invokeinterface(result, recv, pool, index, CHECK); break;
  }
  return;
}



void LinkResolver::resolve_invokehandle(CallInfo& result, constantPoolHandle pool, int index, TRAPS) {
  assert(EnableInvokeDynamic, "");
  // This guy is reached from InterpreterRuntime::resolve_invokehandle.
  KlassHandle  resolved_klass;
  Symbol* method_name = NULL;
  Symbol* method_signature = NULL;
  KlassHandle  current_klass;
  resolve_pool(resolved_klass, method_name,  method_signature, current_klass, pool, index, CHECK);
  if (TraceMethodHandles) {
    ResourceMark rm(THREAD);
    tty->print_cr("resolve_invokehandle %s %s", method_name->as_C_string(), method_signature->as_C_string());
  }
  // 核心方法
  resolve_handle_call(result, resolved_klass, method_name, method_signature, current_klass, CHECK);
}


void LinkResolver::resolve_handle_call(CallInfo& result, KlassHandle resolved_klass,
                                       Symbol* method_name, Symbol* method_signature,
                                       KlassHandle current_klass,
                                       TRAPS) {
  // JSR 292:  this must be an implicitly generated method MethodHandle.invokeExact(*...) or similar
  assert(resolved_klass() == SystemDictionary::MethodHandle_klass(), "");
  assert(MethodHandles::is_signature_polymorphic_name(method_name), "");
  methodHandle resolved_method;
  Handle       resolved_appendix;
  Handle       resolved_method_type;
  // 核心方法
  lookup_polymorphic_method(resolved_method, resolved_klass,
                            method_name, method_signature,
                            current_klass, &resolved_appendix, &resolved_method_type, CHECK);
  result.set_handle(resolved_method, resolved_appendix, resolved_method_type, CHECK);
}


void LinkResolver::lookup_polymorphic_method(methodHandle& result,
                                             KlassHandle klass, Symbol* name, Symbol* full_signature,
                                             KlassHandle current_klass,
                                             Handle *appendix_result_or_null,
                                             Handle *method_type_result,
                                             TRAPS) {
  		// 在MethodHandle中关键的native方法有:invoke、invokeExact、invokeBasic、linkToVirtual、linkToStatic、linkToSpecial、linkToInterface。其中invokeBasic、linkToVirtual、linkToStatic、linkToSpecial、linkToInterface为intrinsic函数执行find_method_handle_intrinsic;invoke、invokeExact执行find_method_handle_invoker。
  
  		//...忽略部分代码
			if (MethodHandles::is_signature_polymorphic_intrinsic(iid)) {
        //...忽略部分代码
         
         result = SystemDictionary::find_method_handle_intrinsic(iid,
                                                              basic_signature,
                                                              CHECK);
        //...忽略部分代码
      }else if (iid == vmIntrinsics::_invokeGeneric
               && !THREAD->is_Compiler_thread()
               && appendix_result_or_null != NULL) {  
        //...忽略部分代码
      result = SystemDictionary::find_method_handle_invoker(name,
                                                            full_signature,
                                                            current_klass,
                                                            &appendix,
                                                            &method_type,
                                                            CHECK);
        //...忽略部分代码
     }
		//...忽略部分代码
}

注:由于JVM是跨平台的虚拟机,不同的操作系统的CPU指令集不同,都或多或少的有特殊指令(针对某种场景的特殊指令,进行了硬件方面的优化)。为了利用不同操作系统特殊指令带来性能提升,HotSpot推出了 intrinsic 函数。对这些方法的调用,会被 HotSpot 虚拟机替换成高效的指令序列。而原本的方法实现则会被忽略掉。HotSpot 虚拟机定义了三百多个 intrinsic,intrinsic函数大多是native函数,这些native函数是可以被内联的,intrinsic函数可以在源码的JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/vmSymbols.hpp文件看到全部声明。

然后看下SystemDictionary::find_method_handle_invoker()

// 转接到java中MethodHandleNatives.linkMethod()
methodHandle SystemDictionary::find_method_handle_invoker(Symbol* name,
                                                          Symbol* signature,
                                                          KlassHandle accessing_klass,
                                                          Handle *appendix_result,
                                                          Handle *method_type_result,
                                                          TRAPS) {
  methodHandle empty;
  assert(EnableInvokeDynamic, "");
  assert(!THREAD->is_Compiler_thread(), "");
  Handle method_type =
    SystemDictionary::find_method_handle_type(signature, accessing_klass, CHECK_(empty));
  if (false) {  // FIXME: Decide if the Java upcall should resolve signatures.
    method_type = java_lang_String::create_from_symbol(signature, CHECK_(empty));
  }

  KlassHandle  mh_klass = SystemDictionary::MethodHandle_klass();
  int ref_kind = JVM_REF_invokeVirtual;
  Handle name_str = StringTable::intern(name, CHECK_(empty));
  objArrayHandle appendix_box = oopFactory::new_objArray(SystemDictionary::Object_klass(), 1, CHECK_(empty));
  assert(appendix_box->obj_at(0) == NULL, "");

  // This should not happen.  JDK code should take care of that.
  if (accessing_klass.is_null() || method_type.is_null()) {
    THROW_MSG_(vmSymbols::java_lang_InternalError(), "bad invokehandle", empty);
  }

  // 调用java方法 java.lang.invoke.MethodHandleNatives::linkMethod(... String, MethodType) -> MemberName
  JavaCallArguments args;
  args.push_oop(accessing_klass()->java_mirror());
  args.push_int(ref_kind);
  args.push_oop(mh_klass()->java_mirror());
  args.push_oop(name_str());
  args.push_oop(method_type());
  args.push_oop(appendix_box());
  JavaValue result(T_OBJECT);
  JavaCalls::call_static(&result,
                         SystemDictionary::MethodHandleNatives_klass(),
                         vmSymbols::linkMethod_name(),
                         vmSymbols::linkMethod_signature(),
                         &args, CHECK_(empty));
  Handle mname(THREAD, (oop) result.get_jobject());
  (*method_type_result) = method_type;
  return unpack_method_and_appendix(mname, accessing_klass, appendix_box, appendix_result, THREAD);
}

​ 至此我们发现invokehandle指令执行到最后会调用java代码中的java.lang.invoke.MethodHandleNatives::linkMethod(... String, MethodType)方法返回一个MemberName对象(MemberName描述一个具体的方法)。然后获取MemberName中描述方法的直接引用保存到运行时常量池中并调用方法,后续再次执行invokehandle指令时就不需要解析了,直接取常量池中缓存的直接引用。

​ 接下来我们利用断点分析下MethodHandleNatives.linkMethod(... String, MethodType)方法返回的MemberName对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SusrqTwe-1654433141933)(/Users/yangxiaofei/Library/Application Support/typora-user-images/image-20220529230519829.png)]

在这里插入图片描述

根据上述断点发现MemberName表达的方法就是我们在2.2中添加了XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames 虚拟机参数来打印隐藏的栈信息。

java.lang.ArithmeticException: / by zero
	at jvm.jit.Animal.calculation(Animal.java:22)
	at java.lang.invoke.LambdaForm$DMH006/654582261.invokeSpecial_000_LII_I(LambdaForm$DMH006:1000014)
	at java.lang.invoke.LambdaForm$BMH001/985934102.reinvoke_001(LambdaForm$BMH001:1000024)
	at java.lang.invoke.LambdaForm$MH012/1654589030.invoke_000_MT(LambdaForm$MH012:1000022)
	at jvm.jit.MethodHandleDemo.main(MethodHandleDemo.java:15)

​ LambdaForm$MH012、LambdaForm$BMH001、LambdaForm$DMH006等类会在根据MethodHandles.Lookup查找创建MethodHandle时动态生成一部分通用的,在MethodHandle首次执行invoke方法进行解析符号引用时动态生成一部分,可以通过添加jvm参数

-Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true -XX:+TraceClassLoading进行验证。LambdaForm$MHXX 系列类中定义的方法入参和返回值需要适配MethodHandle中的方法句柄的类型(MethodType)。所以动态生成LambdaForm$MHXX 系列类的数量与整个工程中MethodHandle的数量呈正相关。

​ 至此MethodHandle.invoke方法的调用栈链路我们已经彻底串联起来了。

2.5、final优化

​ 我们在2.JVM角度看方法调用-反射篇中利用标记final的MethodHandle与直接调用进行过对比,性能直逼直接调用。接下来我们还是使用之前的代码,只是去掉final在运行一下

public class ReflectCallDemo1 {
    static  MethodHandle methodHandle;
    static {

        try {
            Animal animal=new Animal();
            Method eatMethod = animal.getClass().getMethod("calculation", int.class, int.class);
            MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
            methodHandle= publicLookup.unreflect(eatMethod).bindTo(animal);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    static MethodHandle finalMethodHandle=methodHandle;//去除final

    public static void main(String[] args)throws Throwable{
        int count=1;
        long totalTime=0L;
        while(count<=20){
            long start=System.currentTimeMillis();
            for(int i=0;i<100_000_000;i++){
                test();
            }
            //Thread.sleep(10000);
            long result=System.currentTimeMillis()-start;
            System.out.println("第"+count+"次执行时长:"+result+"毫秒");
            totalTime=totalTime+result;
            count++;
        }
        System.out.println("反射调用平均时长:"+totalTime/20+"毫秒");
    }

    public static void test()throws Throwable{
        int aa= (int) finalMethodHandle.invokeExact(2, 3);
    }

}

 打印日志:
第1次执行时长:392毫秒
 ...
第20次执行时长:359毫秒
反射调用平均时长:375毫秒

通过比较我们可以发现是否标记为final对MethodHandle的调用性能影响极大。通过我们上面剖析的MethodHandle.invoke方法的调用栈链路可以发现相比于直接调用MethodHandle.invoke的调用栈还是更为复杂,那想让其性能直逼直接调用的关键点就JIT是否能将上述的整个调用栈链路全部内联,这样MethodHandle.invoke的函数调用开销就等同于直接调用了。接下来我们结合着MethodHandle.invoke的调用栈以及HotSpot源码进行分析:

MethodHandle.invoke调用链路中的第一个方法java.lang.invoke.MethodHandleNatives::linkMethod(... String, MethodType)方法返回一个MemberName对象(MemberName描述一个具体的方法),只有在第一次调用时执行,后续直接取常量池中的缓存。所以我们之间从 LambdaForm$MH012开始看起,我们依次看下LambdaForm$MH012/1654589030.invoke_000_MT、 LambdaForm$BMH001/985934102.reinvoke_001、LambdaForm$DMH006/654582261.invokeSpecial_000_LII_I 这几个方法是否可以被内联

(一)LambdaForm$MH012/1654589030.invoke_000_MT()

final class LambdaForm$MH012 {
    @Hidden
    @Compiled
    @ForceInline
    static int invokeExact_000_MT(Object var0, int var1, int var2, Object var3) {
        Invokers.checkExactType(var0, var3);
        Invokers.checkCustomized(var0);
        MethodHandle var4;
        return (var4 = (MethodHandle)var0).invokeBasic(var1, var2);
    }

    static void dummy() {
        String var10000 = "invokeExact_000_MT=Lambda(a0:L,a1:I,a2:I,a3:L)=>{\n    t4:V=Invokers.checkExactType(a0:L,a3:L);\n    t5:V=Invokers.checkCustomized(a0:L);\n    t6:I=MethodHandle.invokeBasic(a0:L,a1:I,a2:I);t6:I}";
    }
}

首先invokeExact_000_MT被标记了@ForceInline注解其自身是可以被内联的,Invokers.checkExactType()和Invokers.checkCustomized()也是被标记了@ForceInline注解可以内联,所以关键就在MethodHandle.invokeBasic()是否可以内联,虽然MethodHandle.invokeBasic()是native方法但是JIT针对MethodHandle中的native方法都有特殊处理,我们只挑关键的看,C1函数内联的整个流程可以参考国雄大大的这个文章。

JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/c1/c1_GraphBuilder.cpp
  
bool GraphBuilder::try_method_handle_inline(ciMethod* callee) {
  ValueStack* state_before = state()->copy_for_parsing();
  vmIntrinsics::ID iid = callee->intrinsic_id();
  switch (iid) {
  case vmIntrinsics::_invokeBasic:
      // 如果执行的是invokeBasic方法调用
    {
      // 根据操作数栈的深度减去invokeBasic方法的参数数量,获取MethodHandle在操作数栈中的位置
      const int args_base = state()->stack_size() - callee->arg_size();
      // 在操作数栈中获取MethodHandle引用
      ValueType* type = state()->stack_at(args_base)->type();
      // 判断MethodHandle引用是否是常量,如果是则可以放心进行激进优化,如果不是则不进行优化,并打印不优化的原因为receiver not constant(接收器不恒定)
      if (type->is_constant()) {
        // 获取MethodHandle中的vmentry(后面解释),即下一步要调用的方法引用,上例中是LambdaForm$BMH001.reinvoke_001
        ciMethod* target = type->as_ObjectType()->constant_value()->as_method_handle()->get_vmtarget();
      
        if (target->is_static() || target->can_be_statically_bound()) {
           // 因为LambdaForm$BMH001.reinvoke_001是静态方法所以此处重组成 
          // invokestatic <LambdaForm$BMH001.reinvoke_001>  指令并调用 try_inline()。
          // try_inline中会进入try_inline_full(),在此函数中通过是否被@ForceInline标记、是否满足MaxForceInlineLevel、是否是synchronized方法块...等来判断函数是否运行内联
          Bytecodes::Code bc = target->is_static() ? Bytecodes::_invokestatic : Bytecodes::_invokevirtual;
          if (try_inline(target, /*holder_known*/ true, bc)) {
            return true;
          }
        } else {
          print_inlining(target, "not static or statically bindable", /*success*/ false);
        }
      } else {
        print_inlining(callee, "receiver not constant", /*success*/ false);
      }
    }
    break;

  case vmIntrinsics::_linkToVirtual:
  case vmIntrinsics::_linkToStatic:
  case vmIntrinsics::_linkToSpecial:
  case vmIntrinsics::_linkToInterface:
    {
      // 也是类似invokeBasic的特殊处理,在操作数栈中获取目标方法的MemberName,如果是MemberName是常量,再进行一堆判断最后重新组装成invokestatic或invokespecial或invokevirtual或invokeinterface然后调用try_inline();
    }
    }
}

通过上述HotSpot源码分析可以MethodHandle是否被标记为final是其后续调用栈链路能否被内联的关键。

(二)LambdaForm$BMH001/985934102.reinvoke_001()

final class LambdaForm$BMH001 {
    @Hidden
    @Compiled
    @ForceInline
    static int reinvoke_001(Object var0, int var1, int var2) {
        Species_LL var5;
        Object var3 = (var5 = (Species_LL)var0).argL1;
        Object var4 = var5.argL0;
        return ((MethodHandle)var4).invokeBasic(var3, var1, var2);
    }

    static void dummy() {
        String var10000 = "BMH.reinvoke_001=Lambda(a0:L/SpeciesData<LL>,a1:I,a2:I)=>{\n    t3:L=BoundMethodHandle$Species_LL.argL1(a0:L);\n    t4:L=BoundMethodHandle$Species_LL.argL0(a0:L);\n    t5:I=MethodHandle.invokeBasic(t4:L,t3:L,a1:I,a2:I);t5:I}";
    }
}

这里是否内联的关键也是MethodHandle.invokeBasic()是否可以内联,关键在于MethodHandle是常量。

**(三)LambdaForm$DMH006/654582261.invokeSpecial_000_LII_I() **

final class LambdaForm$DMH006 {
    @Hidden
    @Compiled
    @ForceInline
    static int invokeVirtual_001_LII_I(Object var0, Object var1, int var2, int var3) {
        Object var4 = DirectMethodHandle.internalMemberName(var0);
      // 此处MethodHandle.linkToVirtual最终调用的是Animal.calculation()方法
        return MethodHandle.linkToVirtual(var1, var2, var3, (MemberName)var4);
    }

    static void dummy() {
        String var10000 = "DMH.invokeVirtual_001_LII_I=Lambda(a0:L,a1:L,a2:I,a3:I)=>{\n    t4:L=DirectMethodHandle.internalMemberName(a0:L);\n    t5:I=MethodHandle.linkToVirtual(a1:L,a2:I,a3:I,t4:L);t5:I}";
    }
}

这里是否内联的关键也是MethodHandle.linkToVirtual()是否可以内联,根据上面的HotSpot源码分析linkToVirtual是否可以内联的关键是(MemberName)var4是否是常量,而此处的var4始终是个常量,所以Animal.calculation()恒定被内联到invokeVirtual_001_LII_I一侧。

class DirectMethodHandle extends MethodHandle {
    final MemberName member; 	
		@ForceInline
    /*non-public*/ static Object internalMemberName(Object mh) {
        return ((DirectMethodHandle)mh).member;
    } 
} 

至此我们通过上述分析解答了文章开头的第二个问题:为什么MethodHandle声明时要被final修饰,否则性能会大大打折扣?。

最后注意两个地方:

​ ①、什么是vmentry

​ MethodHandle—>form—>vmentry中记录了当前MethodHandle的native方法真正调用的方法

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6S5peHgD-1654433141933)(/Users/yangxiaofei/Library/Application Support/typora-user-images/image-20220605184111316.png)]

HotSpot的JIT会根据常量MethodHandle中的vmentry值重新组装成invokestatic或invokespecial或invokevirtual或invokeinterface然后进行内联分析。

​ ②、MethodHandle.invoke()方法在执行127次以后调用链路会变化,细节可以参考checkCustomized优化。

Hotspot MethodHandle详解 目录 一、编程语言类型 1、解释型与编译型语言 2、静态类型语言与动态类型语言 3、强类型语言和弱类型语言 4、动态语言和静态语言 二、java.lang.invoke 1、JSR-292 2、MethodType 3、MethodHandles.Lookup 4、MethodHandle 5、MethodHandles 三、与Java Reflect的区别 ... 阅读详情

相关推荐

dq轴坐标系下的电路方程

dq轴坐标系下的电路方程 dq轴坐标系是一种常见的电路方程,但是再dq轴坐标系下电路元件的电压电流dq轴存在一定的耦合,本文针对三相电路推导了dq轴坐标系下的电路电路方程进行一个推导,首先先列出来派克变换的公式 派克变换 [AdAqA0]=23[cos⁡(θ)cos⁡(θ−2π/3)cos⁡(θ+2π/3)−sin⁡(θ)−sin⁡(θ−2π/3)−sin⁡(θ+2π/3)1/21/21/2][AaAbAc]\left[\begin{array}{c}A_d\\A_q\\A_0 \end{array}\ri

Mr.Young的博客 1万+

MethodHandle(方法句柄)

MethodHandle(方法句柄)系列之一:MethodHandle和MethodType  阅读此文章的作者建议先了解java反射和动态代理。       java7中为间接调用方法引入了新的api,其中最关键的是java.lang.invoke包,即方法句柄。我们可以看成是java反射的升级版,但是它不反射那样有时候显得冗长、繁重的粗糙。     方法句柄中首先涉

jQuerys的专栏 3056

三级等保网络拓扑图实战:从零搭建符合标准的医院安全架构

本文详细阐述了如何从零开始设计并搭建符合三级等保标准的医院网络安全架构。文章基于“一个中心,三重防护”的核心框架,深入解析了医院网络分区设计、安全域划分、关键安全设备选型与配置等实战要点,并提供了绘制专业网络拓扑图的技巧与等保测评准备指南,旨在帮助医院IT人员系统化地构建既合规又实用的纵深防御体系。

weixin_29326909的博客 502

java handler使用方法_java中的MethodHandler入门

介绍MethodHandler,翻译过来就是方法句柄,是java7提供的jsr292的一部分,为了支持动态方法的调用,主要是java.lang.invoke包。使用public class Client {public static void main(String[] args) throws Throwable {MethodHandles.Lookup lookup = MethodHand...

weixin_28805935的博客 1048

virtuoso根据原理图绘制版图并联接_virtuoso画原理图

Virtuoso Schematic Editor 电路仿真一、Virtuoso Schematic Editor 实验目的:掌握电原理图(schematic)设计输入方法。 [1]启动 IC Design 软件 启动 软件: 开机后运行......2.绘制原理图 新建工程后打开的是默认的原理图文件 SCHEMATIC1 PAG...存盘 完成原理图的绘制后,将原理图存盘. 三 . 用 Check...

weixin_33536190的博客 4143

GOM引擎单机传奇架设全攻略——从DBC2000配置到登录器生成

本文详细解析了使用GOM引擎架设单机传奇的完整流程,从DBC2000的安装配置、服务端部署、引擎控制器设置,到登录器生成与客户端补丁放置。文章重点解决了单机架设中的常见问题,如数据库路径配置、IP地址设置及Key.lic文件处理,旨在帮助玩家快速搭建稳定的个人游戏环境,体验GM乐趣。

weixin_28318011的博客 627

Java7 新特性:MethodHandler,MethodHandles,MethodHandles.Lookup用法详解

相关类介绍 MethodType类 MethodType是不可变类,提供了对方法所有输入参数和输出参数的描述。创建MethodType类实例的方法是调用静态方法MethodType.methodType(),该方法的第一个参数是方法的返回类型,之后的所有参数都是方法的参数类型。MethodType类通过静态方法MethodType.methodType创建。 // 创建一个MethodType类型对象,其返回值是String,第一个参数类型是char,第二个参数类型是char mt = MethodType

qq_37718687的博客 4012

DMTF相关协议(一):Redfish和SMBIOS

Redfish 是一种管理标准,使用超媒体 RESTful 接口中的数据模型表示。该模型以标准的、机器可读的模式表示,消息的有效负载以 JSON 表示。该协议本身利用了 OData v4。由于它是一个超媒体 API,因此能够通过一致的接口表示各种实现。它具有管理数据中心资源、处理事件、长时间任务和发现的机制。

weixin_44032634的博客 2263

JVM角度看方法调用-反射篇

文章目录一、反射调用的原理1.1获取MethodAccessor1.2 native版本MethodAccessor的实现1.3 java版本的MethodAccessor的实现二、反射调用优2.1、为什么反射调用性能低2.2、反射调用性能测试和优化(一)、直接调用(二)、反射调用(三)、反射调用优化版本1(四)、反射调用优化版本2(五)、反射调用优化版本3(六)、MethodHandle的黑魔法(七)、java版本超多态虚方法无法内联问题(八)、总结 JVM角度看方法调用系列文章: 1.JVM角度看方法

yangxiaofei_java的博客 941

运算放大器电路中的T型网络:如何用更小电阻实现高增益并降低噪声?

本文深入解析了运算放大器电路中T型反馈网络的设计与应用。该网络通过三个较小阻值电阻的组合,等效实现高增益,有效规避了传统大反馈电阻带来的显著热噪声问题。文章从数学原理、噪声定量分析到实际设计技巧,系统阐述了如何利用T型网络在实现高增益的同时,显著降低电路噪声并改善整体性能。

banana的博客 318

天地图Api4.0.怎么根据geojson的数据。把一个省的所有市区标记色块和文字

天地图

肖肖肖丽珠的博客 1511

MethodHandle与反射Method区别,invokedynamic指令

MethodHandle与反射Method区别MethodHandle的使用方法和效果上与Reflection都有众多相似之处。不过,它们也有以下这些区别: Reflection和MethodHandle机制本质上都是在模拟方法调用,但是Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。在MethodHandles.Lookup上的三个方

yushui的笔记本 7204

「模型部署系列」ubuntu 使用vllm部署Qwen3-8B模型

解决这个问题的方法正如错误信息中所提示的,需要在命令行中显式地设置。下载单个文件到指定本地文件夹(以下载README.md到当前路径下“dir”目录为例)解决:这表明你正在使用的 Tesla T4 GPU 的计算能力为 7.5,而。,用于获取更详细的 NCCL 调试信息,当所有其他问题排除,可尝试增加。1、下载vllm v0.8.5(此处已经下好了,去仓库拉资源)在下载前,请先通过如下命令安装ModelScope。,这一错误最终致使引擎进程启动失败。更多更丰富的命令行下载选项,可参见。

汪依帆的博客 5044

答复: 通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比(二)

原帖地址:[url]http://www.iteye.com/topic/477934?page=3#1186622[/url] 再顺手转回来免得以后找起来麻烦 [quote="star022"]Java代码 findStatic( TestMethodHandle1.class, // 方法所属类型(Class) "hello", ...

Script Ahead, Code Behind 127

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

JVM中的invokedynamic:从字节码到多语言动态链接协议详解

JVM 凭借字节码实现语言无关性,但面对 Kotlin、Groovy、Scala、JRuby 等动态调用风格各异的语言,传统四条方法调用指令难以统一承载。JDK 7 引入的 invokedynamic 指令,将方法查找逻辑从 JVM 内部抽离为可扩展的三层动态链接协议:字节码指令层定义调用点,链接层通过 Bootstrap Method 创建 CallSite,运行时分派层借助 MethodHandle 完成高效调用。这一机制不仅解决了多语言宿主的方法分派难题,也是 Java 中 Lambda 表达式、字符

weixin_33508411的博客 230

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

iloader 完整 iOS 侧载教程:3 步装好 SideStore

数据线接上了,设备列表里却空空如也;或者 Apple ID 输完密码,弹出一行看不懂的报错。iOS 侧载(把应用装进 iPhone 的非官方方式)对新手最劝退的,就是这两个卡点。iloader 是一款免费开源的桌面工具,登录后它自己完成下载、代码签名和安装,你不用碰证书。 ## 第一次 iOS 侧载安装跑通 这一节带你从装软件走到 SideStore 出现在手机上,网络正常的话全程十来分钟。

gitblog_00615的博客 277

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

JVM--类加载总结

类的卸载过程比较复杂,只有自定义类加载器加载的类才有可能被卸载,因为类的卸载过程需要把方法区中类的数据结构、堆中的对象以及对应的类加载器都卸载,但是类加载器不只是加载这一个类,想APPClassLoader会加载很多类,要把APPClassLoader回收的话,它加载的所有类及类的对象都得回收,这是不可能的。站在程序的角度看,引导类加载器与另外两种类加载器(系统类加载器和扩展类加载器)并不是同一个层次意义上的加载器,引导类加载器是使用C++语言编写而成的,而另外两种类加载器则是使用Java语言编写而成的。

qq_43583691的博客 1540

Java类加载与调用机制深度解析

类加载是JVM执行Java程序的核心机制之一,涉及加载、链接和初始化三个阶段。通过双亲委派模型,JVM确保类加载的安全性和一致性。理解类加载原理有助于优化代码性能,特别是在处理反射调用、动态代理等高阶特性时。方法调用指令如invokevirtual和invokestatic直接影响执行效率,虚方法表(vtable)是实现多态的关键数据结构。在实际开发中,类初始化顺序和死锁问题常导致难以排查的故障,合理使用Holder模式等延迟加载技术可有效避免。掌握这些底层机制,不仅能提升系统性能,还能更好地处理内存泄漏和

weixin_34221654的博客 331

Java技术体系之基础能力篇(五)——深入理解JVM原理

1、Java概述 1.1 Java技术体系   从广义上讲,Kotlin、Clojure、JRuby、Groovy等运行于Java虚拟机上的编程语言及其相关的程序都属于Java技术体系中的一员。如果仅从传统意义上来看,JCP官方所定义的Java技术体系包括了以下几个组成部分: Java程序设计语言 各种硬件平台上的Java虚拟机实现 Class文件格式 Java类库API 来自商业机构和开源社区的第三方Java类库   以上是根据Java各个组成部分的功能来进行划分,如果按照技术所服务的领域来划分,或者

qsmiley10的博客 3360

二、JVM相关

定义:JVM内存模型可以分为线程共享区域(堆、方法区)和线程私有区域(程序计数器、虚拟机栈以及本地方法区)以及直接内存三大块,其中线程共享区域和线程私有区域又被称作运行时数据区域(Runtime Data Area)示意图JVM内存模型和Java内存模型(JMM)的区别**扩展:**与操作系统相对于硬件类似,操作系统需要抽象出内存模型,那么 Java语言也需要抽象出相对于操作系统的内存模型,这就是Java内存模型(JMM)。

恁爹的博客 512

Java反射性能优化:MethodHandle与反射机制对比

Java反射机制作为动态调用的核心技术,在框架开发和动态代理中广泛应用,但其性能开销一直是开发者关注的焦点。反射调用涉及权限检查、参数装箱拆箱、方法签名解析等多个运行时步骤,导致其性能比直接调用慢50-100倍。相比之下,Java 7引入的MethodHandle通过类型安全、调用点缓存和JIT内联优化等特性,显著提升了动态调用的效率。在RPC框架、插件系统等高并发场景中,合理选择反射优化方案(如MethodHandle或代码生成)可以大幅提升系统吞吐量。本文通过性能对比和实际案例,深入解析反射与Metho

weixin_29248653的博客 256

深入理解Java类初始化机制与线程安全

类初始化是JVM加载类过程中的关键阶段,涉及静态变量赋值和静态代码块执行。其核心原理是通过隐藏的初始化锁(init lock)确保线程安全,每个类的clinit方法保证初始化操作仅执行一次。这一机制使得静态代码块成为天然的线程安全区域,在Java并发编程中具有重要价值。典型应用场景包括new实例化、访问静态成员、反射调用等场景,在Spring等框架中广泛用于@PostConstruct注解实现、懒加载控制等高级特性。理解初始化锁与状态标记机制,可以避免生产环境中的死锁问题,优化系统启动性能。

weixin_31031711的博客 122

MethodHandleProxies 源码解析:方法句柄到接口代理的桥梁

反射动态调用虽然灵活,但受限于参数装箱、访问检查,性能损耗明显。MethodHandle 以签名多态调用与 JIT 内联优化,成为现代 Java 运行时替代反射的关键机制。java.lang.invoke.MethodHandleProxies 是连接 MethodHandle 与普通 Java 接口的桥梁:asInterfaceInstance 将方法句柄包装成接口代理,asMethodHandle 则恢复原始句柄。其源码通过 MemberName 解析接口方法,借助私有 Handler 完成参数适配,绕

weixin_34094282的博客 262

Commons Validator vs ValidX:Java校验框架选型与性能实测

在Java后端开发中,参数校验是保障数据完整性的基础环节。从早期的XML规则引擎到现代的注解与流式API,校验框架的演进反映了工程实践对可读性、组合性与性能的更高追求。Apache Commons Validator作为老牌规则引擎,通过外置XML定义表单级校验,适合遗留系统集成与动态规则调整;而新兴的ValidX则利用注解和链式调用实现对象级组合校验,并借助MethodHandle缓存优化运行时性能。二者在功能模型、错误收集、嵌套对象处理及快速失败等维度差异显著。通过JMH基准测试可以发现,在复杂对象树与

weixin_34405354的博客 291

Java反射机制详解:从Class对象到框架原理与实战避坑

在程序运行过程中动态地获取类的结构和能力,是现代框架设计与通用工具开发不可或缺的技术基础。Java通过Class对象将类的字段、方法、构造器、注解等元数据完整暴露给运行期,开发者无需在编译期写死类型,即可实现对象的创建、字段读写和方法调用。这一机制既奠定了Spring依赖注入、MyBatis Mapper代理、JDK动态代理等核心框架的地基,也在序列化、IDE调试、SPI加载等场景中广泛应用。不过,反射带来的灵活性与性能损耗、安全检查缺失、异常包装等问题同样值得重视。本文围绕Java反射机制的核心API、底

weixin_27102543的博客 276

CS-Notes 深入理解 Java 虚拟机:运行时数据区域、垃圾收集、内存分配策略与类加载机制全景

本篇技术指南基于 CS-Notes 笔记仓库中的 Java 虚拟机 文档展开,系统梳理 HotSpot 虚拟机的运行时数据区域(程序计数器、虚拟机栈、堆、方法区、直接内存)、垃圾收集的判定依据与七大收集器、内存分配与 Full GC 触发策略,以及类加载机制与双亲委派模型的实现源码。读完后你将能够解释 `StackOverflowError`/`OutOfMemoryError` 的内存成因、合理

gitblog_00568的博客 557
上一篇: JVM角度看方法调用-反射篇
下一篇: IPv6-基础
躺平程序猿
博客等级 码龄8年 4870粉丝 71原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺平程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值