MethodHandle方法句柄使用分享

简介: JDK 1.7 引入了 `MethodHandle` 类,优化了之前的反射机制性能问题。`MethodHandle` 提供了一种更高效的方法调用机制,通过 `MethodType` 和 `Lookup` 对象实现。具体流程包括:创建 `MethodType` 获取方法签名,通过 `Lookup` 查找方法对应的 `MethodHandle`,最后调用方法。此外,通过 `ConstantCallSite` 进一步优化性能,利用其不变性特点减少运行时开销。实验结果显示,使用 `ConstantCallSite` 显著提升了方法调用速度。

一.总述

JDK1.7为间接调用方法提供了MethodHandle类,即方法句柄。是对之前JDK1.7之前反射性能不佳的优化手段之一 代码案例如下

jspackage

代码解读

复制代码

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import org.junit.jupiter.api.Test;

public class TestMethodHandlesLookup {
private static void printData () {
System.out.println("print data method.");
}

@Test
public void testMethodData() throws Throwable {

/*
* 使用反射提前获取类型
*/

MethodHandles.Lookup methodHandles=MethodHandles.lookup().in(TestMethodHandlesLookup.class);
MethodType type= MethodType.methodType(void.class);
MethodHandle method = methodHandles.findStatic(TestMethodHandlesLookup.class, "printData", type);
method.invokeExact();

}

}

用MethodHandle调用方法的流程为:

  1. 创建MethodType,获取指定方法的签名
  2. 在Lookup中查找MethodType的方法句柄MethodHandle
  3. 传入方法参数通过MethodHandle调用方法

创建MethodType

MethodType表示一个方法类型的对象,每个MethodHandle都有一个MethodType实例,MethodType用来指明方法的返回类型和参数类型。其有多个工厂方法的重载``

jsstatic

代码解读

复制代码

     static MethodType methodType(Class<?> rtype, Class<?> ptype0) 
     static MethodType methodType(Class<?> rtype, Class<?>[] ptypes) 
     static MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes)    
     static MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) 
     static MethodType methodType(Class<?> rtype, MethodType ptypes)

rtType 表示方法的返回类型,ptypes 表示方法请求参数

创建Lookup

MethodHandle.Lookup可以通过相应的findxxx方法得到相应的MethodHandle,相当于MethodHandle的工厂方法。查找对象上的工厂方法对应于方法、构造函数和字段的所有主要用例。下面是官方API文档对findxxx的说明,这些工厂方法和结果方法处理的行为之间的对应关系: findStatic相当于得到的是一个static方法的句柄,findVirtual找的是普通方法 findSpecial查找私有方法、超类的方法或隐藏的方法(在子类中被重写的方法)

第一种方式 使用工厂创建LookUp

js

代码解读

复制代码

MethodHandles.Lookup methodHandles=MethodHandles.lookup();

第二种方式 使用Unsafe来获取LookUp,如果考虑性能个人推荐这种方式实现。 如果权限报错,请添参数启动 --add-opens java.base/sun.misc=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED

js

代码解读

复制代码

Unsafe unsafe=null;
Field unsafefiled = Unsafe.class.getDeclaredField("theUnsafe");
unsafefiled.setAccessible(true);
unsafe= (Unsafe) unsafefiled.get(null);
Lookup lookup=null;
try {
Field sd = Lookup.class.getDeclaredField("IMPL_LOOKUP");
sd.setAccessible(true);
long offeset = unsafe.staticFieldOffset(sd);
lookup = (Lookup) unsafe.getObject(Lookup.class, offeset);
System.out.println("避免反射来创建lookup失败**");**
} catch (Exception e) {
lookup= MethodHandles.lookup();
}

第三种方式, 可以通过反射构造函数来实现

js

代码解读

复制代码

public static MethodHandles.Lookup getLookupByReflection(Class<?> cls) {
try {
Constructor<Lookup> constructor =
MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
return constructor.newInstance( cls, -1 );// 相当于lookup.in(Class);
} catch (Exception e) {
return null
}

创建MethodHandle方法句柄

  1. 使用Lookup对象的findStatic(), findVirtual(), findSpecial(), findConstructor()等方法来查找并获取目标方法的MethodHandle对象
  2. 如果MethodHandle指向的是实例方法,可以使用MethodHandle对象的bindTo()方法将其绑定到目标实例上
  3. 使用MethodHandle对象的invoke()invokeExact()invokeWithArguments()等方法来调用目标方法
  • invoke():提供类型适配的灵活调用,允许在运行时转换参数类型。
  • invokeExact():提供严格的类型检查,要求参数类型与目标方法完全匹配。
  • invokeWithArguments():允许使用列表作为参数进行调用,提供了更大的灵活

与反射的区别

性能:MethodHandle通常比反射更快,因为它绕过了许多反射的额外开销。

类型安全:MethodHandle在编译时会进行类型检查,而反射在运行时进行类型检查,可能导致ClassCastException等异常。

用法:反射需要先获取Method对象,而MethodHandle直接对方法进行引用

总结:但是实际使用起来性能并没有明显多大提升,但是我们可以利用callSite来提升性能

普通案例:

js

代码解读

复制代码

@Test
public void testMethodData() throws Throwable {
/*
* 使用反射提前获取类型
*/
MethodHandles.Lookup methodHandles=MethodHandles.lookup().in(LooKUpTest.class);
MethodType type= MethodType.methodType(void.class);
MethodHandle method = methodHandles.findStatic(LooKUpTest.class, "printData", type);
long start=System.currentTimeMillis();
for (int i = 0; i <100000; i++) {
method.invokeExact();
}
System.out.println(System.currentTimeMillis()-start);
}

优化案例:ConstantCallSitejava.lang.invoke包中的一个类,它是CallSite的一个实现,用于表示一个调用点,该调用点总是返回一个常量值。以下是一个简单的使用ConstantCallSite的例子CallSite实现类的简要概述:

  1. ConstantCallSite:
  • ConstantCallSite表示一个调用点,该调用点的目标方法句柄永远不会改变。
  • 它通常用于优化已知永远不会改变的目标方法的调用。
  1. MutableCallSite:
  • MutableCallSite允许在运行时改变其目标方法句柄。
  • 它适用于需要动态改变方法行为的场景。
  1. VolatileCallSite:
  • VolatileCallSiteMutableCallSite的一个子类,其目标方法句柄的所有更新都是原子的,并且每次调用时都会重新读取目标方法句柄。
  • 它适用于多线程环境中,其中目标方法句柄可能会被多个线程并发修改。

js

代码解读

复制代码

@Test
public void testMethodDataConstant() throws Throwable {
MethodHandles.Lookup methodHandles=MethodHandles.lookup().in(LooKUpTest.class);
MethodType type= MethodType.methodType(void.class);
MethodHandle method = methodHandles.findStatic(LooKUpTest.class, "printData", type);
ConstantCallSite callSite=new ConstantCallSite(method);
long start=System.currentTimeMillis();
for (int i = 0; i <100000; i++) {
 callSite.dynamicInvoker();
}
System.out.println(System.currentTimeMillis()-start);
}

案例测试验证 性能比较

js

代码解读

复制代码

@Test
public void testData() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
long startTime = System.nanoTime();
MethodType tyep = MethodType.methodType(void.class);
// 不使用in方法查找
MethodHandle mh = lookup.findStatic(LookupPerformanceTest.class, "dummyMethod", tyep);
for (int i = 0; i < 1000000; i++) {
// Method method = LookupPerformanceTest.class.getMethod("dummyMethod");
// MethodHandle mh = lookup.unreflect(method);
mh.invokeExact();
}
long endTime = System.nanoTime();
System.out.println("LookUp不使用in方法耗时: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms");
startTime = System.nanoTime();
ConstantCallSite callSite=new ConstantCallSite(mh);
for (int i = 0; i < 1000000; i++) {
callSite.dynamicInvoker();
}
endTime = System.nanoTime();
System.out.println("使用ConstantCallSite方法耗时: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms");
startTime = System.nanoTime();
lookup.in(LookupPerformanceTest.class); //绑定查找类型
// 使用in方法查找
MethodHandle mh1 = lookup.findStatic(LookupPerformanceTest.class, "dummyMethod", tyep);
for (int i = 0; i < 1000000; i++) {
// Method method = LookupPerformanceTest.class.getMethod("dummyMethod");
mh1.invokeExact();
}
endTime = System.nanoTime();
System.out.println("Lookup使用in方法耗时: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms");
}

为什么ConstantCallSite 性能能够提升这么多。ConstantCallSite 是一种特殊的 CallSite,它被设计为在运行时不会改变其目标方法句柄。这种实现采取的优化手段包括:

  1. 不变性保证:由于 ConstantCallSite 的目标方法句柄在创建后不会改变,JVM 可以应用特定的优化策略。例如,它可以假定调用该 CallSite 的代码总是执行相同的操作,因此可以省去查找和更新目标方法句柄的开销。
  2. 内联优化:JVM 编译器可以将 ConstantCallSite 的调用内联到调用者的代码中。这意味着调用 CallSite 的代码可以直接转换为目标方法句柄的代码,从而避免了调用开销。
  3. 缓存目标方法:因为 ConstantCallSite 的目标方法句柄是固定的,JVM 可以将其缓存在方法调用链中,从而在每次调用时直接跳转到目标方法,而不是再次解析方法句柄。
  4. 减少安全检查:由于目标方法句柄是固定的,JVM 可能会减少或省略一些安全检查,例如,检查是否有权限调用特定的方法。

先配置添加打印JIT日志参数

  1. -XX:+PrintCompilation
  2. -XX:+UnlockDiagnosticVMOptions
  3. -XX:+PrintInlining

js

代码解读

复制代码

@Test
public void optionData() throws NoSuchMethodException, IllegalAccessException {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType tyep = MethodType.methodType(void.class);
// 不使用in方法查找
MethodHandle mh = lookup.findStatic(LookupPerformanceTest.class, "dummyMethod", tyep);
ConstantCallSite callSite=new ConstantCallSite(mh);
callSite.dynamicInvoker();
}



转载来源:https://juejin.cn/post/7412819188853637183

相关文章
|
Java 数据库连接 API
java7新特性之方法句柄MethodHandle使用
java7新特性之方法句柄MethodHandle使用
326 0
|
2月前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
43676 72
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
自然语言处理 算法 大数据
Python大数据:jieba分词,词频统计
实验目的 学习如何读取一个文件 学习如何使用DataFrame 学习jieba中文分词组件及停用词处理原理 了解Jupyter Notebook 概念 中文分词 在自然语言处理过程中,为了能更好地处理句子,往往需要把句子拆开分成一个一个的词语,这样能更好的分析句子的特性,这个过程叫就叫做分词。
10110 0
|
9月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
1027 1
使用Spring的@Retryable注解进行自动重试
|
安全 Java
【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门
前言 你是否在线程池工具类里看到过它的身影? 你是否会好奇LinkedBlockingQueue是啥呢? 没有关系,小手手点上关注,跟上主播的节奏。 什么是LinkedBlockingQueue? ...
607 1
【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门
|
8月前
|
canal 关系型数据库 MySQL
数据同步神器-Canal
Canal是阿里巴巴开源的MySQL增量日志解析工具,通过模拟MySQL主从复制机制,实时捕获数据库变更,实现数据同步至Kafka、Elasticsearch等系统,广泛应用于数据同步、监控、备份与迁移场景。
6328 5
|
存储 安全 Java
深入探索Java并发编程:ArrayBlockingQueue详解
深入探索Java并发编程:ArrayBlockingQueue详解
|
12月前
|
人工智能 Java 数据库
如何保证接口幂等性?
在分布式系统中,接口幂等性至关重要。本文详解其定义、重要性及实现方案,包括唯一索引、Token机制、分布式锁、状态机与版本号机制,并提供最佳实践建议,助你提升系统可靠性与用户体验。
2340 1