深入理解JVM字节码:invokedynamic

invokedynamic虽然是一个从jdk1.7开始引入的字节码,但是,它对于很多Javaer来说都是一个新鲜玩意,但是其实这个字节码在目前的jdk内部扮演着一些关键角色。

那么今天这个主要是填坑,因为之前在字节码的那篇文章中我说过,我会补上invokedynamic的部分,虽然我感觉写起来有点麻烦,但是不想鸽太久,所以简单给大家分享一下我对这个字节码的理解。As always,仅供参考。

还有就是,如果没有特殊说明,所有的代码的版本都是Java24。

从案例来简单认识invokedynamic

invokedynamic这条指令和之前说过的invokestaticinvokevirtual这类指令相似,都是用来调用方法的。简单来说,它的作用就是将原本在编译期间确认好的方法调用,延迟到运行期间才进行动态决定。

而什么时候会在class文件里看见这个指令呢,目前最常见的场景就是lambda,或者说方法引用,比如这样的代码:

java 复制代码
public class ShowMeInvokeDynamic {
	public static void main(String[] args) {
		Consumer<String> printer = System.out::println;
		printer.accept("Hello world");
	}
}

这是目前大部分Javaer喜欢的写法,那么直接编译这段代码,通过javap来观测,大概会看到这样的东西:

这就是我们想要看到的东西,那我们再去constant pool(后文简称cp)中找一下这一个entry,如下:

可以看到,这是一个方法的描述,我们来看一下第20条entry,可以简单的分为三部分,那么先解析一下这个方法是什么样的:

  • accept:方法名
  • (Ljava/io/PrintStream;):表明该方法接收的参数为PrintStream
  • Ljava/util/function/Consumer:表明该方法的返回值为Consumer

说句题外话,这里的L...代表的是引用类型。那么很明显,是这么一个方法

java 复制代码
Consumer accept(PrintStream arg) {...}

那么其实看起来很奇怪,似乎和我们的目标lambda完全偏离了,按照代码里面的lambda来说,应该只是一个简单的Consumer形式的方法就好了。因为实际上,这个方法是给VM调用的,它的意义是让VM知道,我们需要一个Consumer的实现类,并且其中包含一个方法,这个方法的参数为PrintStream,名字为accept,返回值为void

这就涉及到了invokedynamic这条指令本身的用处:当执行到这条指令的时候,它会从操作数栈上拿走一些需要的参数,然后调用一个所谓的引导方法(BootstrapMethod)来获取一个CallSite

那么什么是BootstrapMethodCallSite,暂时按下不表,只需要知道invokedynamic最终会产生一个东西,并被放置到栈上就可以了。

现在再来看到这一整个字节码,从头到尾来解析一遍就很清晰了:

  • 0~7:这一系列的操作可以说是对应了System.out::println这一行代码,获取到System的out变量,然后会有一个隐式的Objects.requireNonNull。这里想说一下,这个null-check是javac编译器自动插入的,实际上在方法内部,javac为了保证程序的正常运行,会隐式的插入很多的null-check。它会返回out本身,所以这里需要把这个重复的out给pop掉,此时operand stack就剩下了一个out变量
  • 8:通过invokedynamic生成了一个Consumer对象
  • 13~14:可以看出来这是把CallSite给保存了起来,那么这个main方法的locals为2,第0个就是args数组,第1个就是我们定义的pointer这个Consumer
  • 15~22:这里对应的就是printer.accept()这一个方法,然后就正常结束

还有一个比较有意思的点,并且也可以关联到我之前写过的一篇文章,那就是这里的null-check,在Java9之前,javac插入的null-check并不是Objects.requireNonNull,而是一个getClass方法。至于后续版本替换成了Objects.requireNonNull是因为,这个静态方法是一个标准的null-check实践,并且它是一个被intrinsic优化过的方法,相比于直接的getClass,性能会好上不少。关于intrinsic,可以翻看我之前的《从toArray窥探JVM》。

那么出现这个invokedynamic的作用是什么呢?

我们知道,在jdk1.8之前,也就是没有lambda之前,我们想要直接返回一个interface,只能通过匿名内部类来实现,比如:

java 复制代码
public class WhenThereIsNoLambda {
	public static void main(String[] args) {
		Consumer<String> printer = new Consumer<String> {
			@Override public void accept(String str) {
				System.out.println(str);
			}
		}
	}
}

而Javac会为我们生成一个对应的class文件,像这样:

而lambda的出现,让我们可以直接用方法引用的形式来表示一个FunctionalInterface的实现,把它改造成lambda,那么我们就可以看到没有显式的class文件被生成,所以,通过invokedynamic的形式实现的lambda可以为我们省略掉额外的class文件的开销。

那么,具体是怎么实现的呢?那就请继续往下看。

invokedynamic & Java Lambda

此时,需要回到字节码那部分,前面说到,invokedynamic最终需要去通过BootstrapMethod来生成一个CallSite,而心细的朋友会发现,它既然生成的是CallSite,那为什么后面的字节码,我说它是直接把invokedynamic的结果给store到了Consumer类型的printer去了,这当中是有歧义的,因为CallSite很明显不是一个Consumer,那么其中肯定是有一些什么转换,让CallSite变成了我们需要的Consumer

后面,我们简称invokedynamicindy

那么现在,我们来看看所谓的BootstrapMethod以及CallSite

BootstrapMethod

在最初的那份class文件中,通过javap我们其实可以看到,完整的class文件多出了一块BootstrapMethods,这就是我们需要的东西,如下图:

可以看到,这里只有一个entry,关联的是LambdaMetafactory类的metafactory方法,并且这个方法参数列表非常的长,但是实际给到的参数只有三个,这又是为什么呢?

这里实际上可以这么区分,前三个属于是JVM动态提供的,后三个是编译器静态提供的。怎么理解呢,前三个属于是在运行期间,JVM来动态获取的,也是每个BSM必须要有的三个参数,因为JVM需要这三个东西来找到具体的方法实现;而后面三个,某种程度来说是可以不需要的。这方面就不多展开,只是防止大伙有疑惑,了解一下即可。

那么首先,我们先去看这个方法的源码,由于在ide中打开会看到,有非常多的冗余代码,那么就用简单的伪代码表示即可:

java 复制代码
public static CallSite metafactory(...) {
	AbstractValidatingLambdaMetafactory mf = creatingInnerClassLambdaMetafactory(...);
	mf.validateMetafactoryArgs();
	return mf.buildCallSite();
}

实际上的关键步骤只有:

  1. 创建一个InnerClassLambdaMetafactory
  2. 通过这个Metafactory创建一个CallSite

那么找到这个InnerClassLambdaMetafactory类,从名字我们可以猜测,它实际上也是通过InnerClass也就是内部类的形式来实现Lambda的一个factory,从它的源码注释也可以看到:

那么,直接看到它的buildCallSite方法:

看上去嵌套了挺多的,其实逻辑非常简单。

首先看到spinInnerClass这个方法,根据它的注释总结一下它的作用就是:获取一个class,这个class可能是

  • 从CDS中获取
  • 动态生成

为了直观一点,源码如下:

CDS不是本文的重点,简单介绍一下,全称是class data sharing ,用来减少JVM应用的启动时间和内存占用,从名字就知道,是一份可以在多个VM之间共享的class data

这里还有一点要提的就是,实际上是可以看到这些动态生成的类的,在运行的时候配置一个参数 -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles即可。配置了这一个参数之后,所有动态生成的lambda实现类都会在项目的根目录下的DUMP_LAMBDA_PROXY_CLASS_FILES文件里存放。

回到正题,一般来说,大部分情况下我们都会直接走到动态生成这一步,那么我们看到generateInnerClass这个方法,这个方法体比较长,为了不影响观感,我就不打算贴源码了,用一段简单的伪代码来表示如下:

java 复制代码
private Class<?> generateInnerClass() throws LambdaConversionException {
	List<ClassDesc> interfaces = decideInterfaces();
	byte[] classBytes = buildClassFileData();
	defineGeneratedClassInTheVM();
	return caller.makeHiddenClassDefiner(...).defineClass(...);
}

这里其实大概从伪代码的命名就可以看出具体的作用,但是这里打算聊一下第二步。

buildClassFileData是怎么build的

讲这一步之前提一嘴第一步,它主要是解决这样的情况:

java 复制代码
Consumer<String> printer = (Consumer<String> & MarkInterfaceWithoutAPI) str -> {};

这时候,这个lambda表示的接口就有两个,一个是Consumer,一个是MarkInterfaceWithoutAPI,但是这里需要注意的是,不管这里有多少个接口,总共只能有一个方法声明,因为lambda只能表示一个方法实现。

所以,第一步就是把所有这些接口给收集起来,但是这些接口全部加起来只能有一个未实现的方法。

现在回到第二步这里。

通过名字很明显,就是来生成一个类的数据。我们用c来简称这个类。

首先,它会先处理被lambda捕获的变量。什么叫被lambda捕获,看到这样的代码:

java 复制代码
final int x = 42;
Runnable r = () -> {System.out.println(x);}

那么此时,这个x就是被捕获的变量,在这一步,会为这些变量生成对应的private final变量,并生成对应的构造器来赋值。

所以此时,这个c大概会长这样:

java 复制代码
public class c {
	private final YourType yourField;
	...
	public c(YourType yourField, ...) {...}
}

然后,会生成lambda的sam(全称Single Abstrac Method)方法,我们直接贴一下部分源码:

java 复制代码
clb.withMethodBody(interfaceMethodName, methodDesc(interfaceMethodtype). ACC_PUBLIC,
	forwardingMethod(interfaceMethodType));

可以看到就是生成一个方法,重点就是forwardingMethod这里,它的注释告知了它的作用:生成一个调用lambda实现的方法。直观一点,我们用伪代码来表示:

java 复制代码
public class c {
	// fields...
	public InterfaceMethodType interfaceMethodName(Args arg) {
		invokeYourLambdaByStaticCall(convertIfNeeded(args));
	}
}

这么看就比较直观了,所以,我们可以知道我们的自定义lambda是通过间接调用来实现的。


然后,这个类就会被创建并加载到vm中。只是可以关注的是它这里的方法名:makeHiddenClassDefiner。注意到它叫做hiddenClass,这种类和普通的类,相比于普通的类,这种类就像它的名字,它是hidden的,也就是它不能被其他的类发现。

那么,它的优势就是,不会被应用代码错误的引用,并且它有个特点,就是它的生命周期和它的Class对象绑定,如果这个Class是unreachable的,那么这个类可以直接被卸载,也就可以让metaspace的空间不会过于吃紧。

回到CallSite

那么这个内部类从此被创建,这时候就会拿着这个类去创建一个CallSite,看回到buildCallSite的源码,创建出的CallSite会链接到某些代码,区别就在于对于捕获/非捕获的lambda,链接到的代码位置不同。

把情况总结一下,如下: 首先,对于所有lambda,都是生成一个新的inner class,这一点是没变的。

  • 对于捕获lambda callsite链接到inner class的构造函数
  • 对于非捕获lambda
    • 如果开启了disable eager initialization 在inner class中会单独生成一个private static final的静态instance,此时,callsite会链接到一个static getter
    • 如果没有开启disable eager initialization callsite会链接到一个pre-computed的对象,也就是说,会在第一次碰到indy的时候创建这个对象,后续的访问就直接返回这个提前计算好的对象

对于CallSite,本质上它就是一个MethodHandle的包装类。

得到CallSite之后

实际上,JVM内部实现也是通过方法调用来解析indy指令的,在对应的方法内部,在拿到CallSite之后,后续还有一些操作。

简单来说的话,就是JVM拿到CallSite之后,会存入当前方法的一个常量池缓存 里面,这个缓存里本身会有indy,然后把这个CallSite和它包装的MethodHandle给包装成一个高效率的入口,这里的包装,主要就是通过一个Invoker$Holder类来做统一的入口。

这里的这个Invoker$Holder实际上就是很多文章会提到的适配器,也就是,indy实际上最后会关联到一块调用Invoker$Holder的入口方法的代码,可以简单的理解成一小段机器码,只有一个作用,就是发起对Invoker$Holder的方法调用,通过它的方法去调用CallSite包装的MethodHandle

这一块不再深入聊,大伙只要知道,在拿到CallSite之后,vm会对它做一系列的操作,然后整合成一个高性能的方法,再和indy关联起来方便后续调用。如果想要深入源码去看具体的步骤的话,具体的方法入口为InterpreterRuntime::resolve_invokedynamic方法,这是在hotspot里的源码,但是还是不建议去看,没啥太大意义。

短暂的总结

所以,把上面的所有步骤总结一下,可以得到这么一个流程:

  • 代码运行期间碰到一个没有解析过的indy指令
  • 根据indy去找到ConstantPool的对应的entry,然后触发对应的BootstrapMethod(BSM)
  • 以lambda为例,触发LambdaMetafactory.metafactory,获取到一个CallSite
  • vm通过统一的方法来处理callsite,也就是前面说的linkToTargetMethod
  • 后续再碰到这一个indy指令的时候,vm知道它被解析过,直接调用对应的方法即可

所以可以看到,indy带来的优势就是全部都是动态执行的,对于lambda来说,indy通过针对不同形式的lambda进行特殊处理,来最大程度的优化lambda实现,并且结合hidden class这一特性来降低metaspace的内存压力。

这也是indy相比于传统的invoke指令的最大区别,传统的invoke指令,它们的共同点就是,它们具体的符号引用是要在编译期间就解析完并固定在类文件里的。indy的出现赋予了Java更强的动态能力,它可以让方法引用的解析真正的推迟到了运行期间,并通过繁琐的优化来获取和普通调用相差无两的性能。

indy在jdk中的其他应用

还有一个比较常见的就是string拼接的场景,比如:

java 复制代码
String s = randomStr() + randomStr();

在Java9之前,这段代码会生成一个StringBuilder来进行拼接,在Java9之后,则使用indy来动态获取要拼接的字符串,直接进行字符串的拼接,而不需要额外创建一个StringBuilder

通过javac后javap可以很清晰的看见,篇幅问题这里不贴全图了,只看Java24下的编译结果:

可以看到它对应的BSM是另外一个方法,StringConcatFactory.makeConcatWithConstants

具体的就不深入看了,只要知道从Java9之后,字符串拼接的实现就是:分析这个拼接行为,然后把所有的常量部分提取出来,对于其他动态参数,用一个特殊字符作为占位符,然后形成一个recipe字符串。比如:

java 复制代码
String s = "UserName:" + name + " Age:" + age;
// Java 9之前
new StringBuilder().append("UserName").append(name).append(" Age").append(age);
// Java 9之后
invokedynamic makeConcatWithConstants("UserName: \u0001 Age: \u0001");

而在这个makeConcatWithConstants内部,还会根据拼接的字符串的复杂度选择不同的策略:

  • String::concat 如果recipe非常简单,那么会采用这种
  • StringBuilder 如果是比较复杂的recipe,可能会采用这种
  • 字节/字符数组拼接 在Java11之后的版本,绝大多数的场景下都会采用这个策略。它会先遍历整个字符串,然后精确的计算出最终字符串的长度,然后直接分配一个大小刚刚好的byte[],然后直接把内容复制进去

具体可以看到源码里面的这部分,很明显:

所以,可以看到indy的出现确实是增强了很多Java内部的动态能力。

怎么在日常开发中使用

那么,前面说到了indy这么多的好处,怎么在日常开发中用上这个东西呢?

答案是做不到。

因为,字节码是由javac编译器生成的,所以,什么东西会通过indy来实现,是由javac决定的。并且前面说到,indy需要绑定BSM,我们是没有办法在代码中手动针对某个方法,让javac为他生成一条indy,并调用我们自定义的BSM的。

但是,虽然我们没有办法控制javac,但是我们可以手动创建字节码。那么,这就是我们能在日常开发中,使用到indy的唯一手段。

那么我们结合前面说到的lambda的实现,特别是在buildCallSite源码里有这么一部分: 重点在这个pre-computing,这个是不是非常符合我们需要的经典场景lazy construction

那么说明,可以通过类似lambda的实现来实现惰性单例,惰性单例需要有这么几个特点:

  • 自定义Supplier
  • 惰性
  • 单例

其实关键特性就在于这个自定义Supplier,因为在Java9之后的VarHandle已经可以很好的实现高效的DCL,但是DCL的缺点就在于,我们必须硬编码,也就是固定一个值,而不能在运行期间动态的修改构造函数的参数值。

那么参考lambda,我们尝试来搞一个使用indy实现惰性单例的工具。

回顾lambda的实现,我们大概需要:

  • 根据需要创建类
  • 通过indy让单例接口的get方法链接到BSM
  • 想办法实现pre-computing

那么后面就大致给出代码以及部分解析。

首先,我们需要有一个接口,并且只能有一个方法,那其实Supplier本身就可以很好的满足我们的需要。那么定义这么一个接口:

java 复制代码
interface LazySupplier<T> extends Supplier<T> {
	@Override T get();
	public static <T> LazySupplier<T> of(Supplier<T> s) {
		return LazySupplierMetafactory.of(s);
	}
}

很明显,LazySupplierMetafactory参考了LambdaMetafactory

java 复制代码
public class LazySupplierMetafactory {
	// indy关联的bsm方法
	public static CallSite indyBsm(Lookup lk, String name, MethodType mt) {...}
	// 参考 LambdaMetafactory的内部逻辑
	// 其实核心就是生成内部类 然后把这个类的get方法修改为indy 然后加载这个类
	public static <T> LazySupplier<T> of(Supplier<T> s) {...}
}

核心其实就是生成内部类这里,因为我们需要在这里来实现LazySupplier接口的get方法,通过这个get方法来把我们实际的supplier给替换掉这个实现。可能有点绕,以防有的朋友搞不明白,搞点简单的代码最直观:

java 复制代码
LazySupplier<String> sls = LazySupplier.of(() -> "returned by...");
// 为这个接口动态生成一个实现类,比如
public class DynamicInnerClass_1 implements LazySupplier<String> {
	@Override String get() {
		invokedynamic OurCustomBSM;
		// 在这里手动填入indy
		// 那么当我们的代码里遇见 sls.get() 的时候
		// 会触发 BSM
		// 那么 BSM会帮我们做一些处理,上面的 indy就会消失了
		return "returned by...";
	}
}

相信这段简单的代码就可以很好的解释这个工具类的核心了,那么我们还需要实现的就是:

  1. 类生成逻辑
  2. BSM

在文章最后我会贴完整的代码,本来我是想贴个github链接的,但是我的github还有很多其他杂七杂八的东西,就不贴了。这里就简单的解释一下大致的逻辑:

java 复制代码
private static final Map<String, Supplier<?>> supplierMap;
public static Class<?> generateClass() {
	// LazySupplierMetafactory.class.getPackage() + LazySupplier.class.getName() + _1
	String cName = getGeneratedClassName();
	
	// todo:这里还需要把supplier存到一个map里面
	
	// 借助 Java 24的class file api
	// 大致步骤为
	// 1. 定义父接口为 LazySupplier
	// 2. 创建 get 方法的实现 里面的指令就是 invokedynamic
	byte[] classByte = generateClassByte(cName);
	return MethodHandles.lookup().defineClass(classByte)
	.newInstance();
}

目的应该还是比较清晰直观的。这里需要提到一点,就是注释说到的,把Supplier暂存到一个map中,存起来是为了后续调用BSM的时候,以全局唯一的classname当做key来获取对应的Supplier来生成对象。所以上面的代码缺了个map的定义,大家知道就好。

那么最后就是对应的BSM,也是比较简单的,大概如下:

java 复制代码
public static CallSite indyBSM(Lookup lk, String name, MethodType mt) {
	Class<?> c = lk.lookupClass();
	// supplierRegistry就是前面说的那个map
	Supplier<?> s = supplierRegistry.remove(c.getName());
	Objects.requireNonNull(s);
	Object preComputingInst = s.get();
	return new ConstantCallSite(MethodHandles.constant(mt.returnType(),
	preComputingInst));
}

同样,尽量简单的表达大致的框架即可,可以看到和我最前面截图出来的,lambda的实现的那部分代码非常的类似。

那么到这里,我们实现的这个LazySupplier接口就实现了。在代码里,我们不需要硬编码它的实现类,而是在运行期间让vm为我们动态的创建实现类,并借助vm处理indy指令的线程安全性来避免手动管理并发问题。

但是,这种方法的缺点就在于,我们这里是直接定义的普通类,而不是lambda的那种hidden class,这里直接改成hidden class也可以,只是稍微修改一下字节码即可。这里就不做实现了,大家可以自行去试试。

这里可能大伙有个疑问就是,如果是hidden class,它可以直接去访问Metafactory里面的map吗?答案是可以的,相关的权限处理问题都被lookup给处理掉了。关于hidden class,更多的也需要大伙自行去了解。

还有一个点就在于,可能会有人觉得,为什么不做成:所有LazySupplier的实现类共用同一个,把Supplier做成构造参数,这样就可以避免类过多的问题。但是,这种做法是不可行的,因为indy实际上绑定的是类的行为,简单来说就是,这种实现方法只会对代码中的第一个LazySupplier.get调用有效,后续所有关联了其他Supplier的实现类都是不能正确返回想要的对象的,大家也可以自行去实验一下。这里就不多说了。

总结

至此,个人认为关于indy的内容,已经基本介绍完毕了,其实关于这个opcode,更多的还是了解即可,在大部分的开发场景下并不需要使用到这一个字节码,但是它对Java的意义还是挺大的,因为正如前文所说,indy赋予了Java更强的动态能力,并且可以通过一些巧妙的手段为我们日常开发提供一些巧思。

完整的LazySupplier

java 复制代码
public interface LazySupplier<T> extends Supplier<T> {
	public static LazySupplier<T> of(Supplier<T> s) {
		return LazySupplierMetafactory.of(s);
	}
}

public class LazySupplierMetafactory {
    private static final ConcurrentHashMap<String, Supplier<?>> supplierRegistry = new ConcurrentHashMap<>();
    private static final AtomicInteger counter = new AtomicInteger();
    private static final String baseName = LazySupplier.class.getName() + "Impl$";

    public static <T> LazySupplier<T> of(Supplier<T> s) {
        String curClassName = className();
        supplierRegistry.put(curClassName, s);
        try {
            Class<?> generatedClass = generateClass(curClassName);
            return (LazySupplier)(generatedClass.getDeclaredConstructor().newInstance());
        } catch (Exception e) {
            supplierRegistry.remove(curClassName);
            throw new RuntimeException(e);
        }
    }

    private static String className() {
        return baseName + counter.getAndIncrement();
    }

    private static final ClassDesc cdLazySingleton = ClassDesc.of(LazySupplier.class.getName());
    private static final ClassDesc cdBsmProvider = ClassDesc.of(LazySupplierMetafactory.class.getName());

    private static Class<?> generateClass(String cName) throws Exception {
        final ClassDesc cdThisClass = ClassDesc.of(cName);
        byte[] classBytes = ClassFile.of().build(cdThisClass, classBuilder -> {
            classBuilder.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_FINAL | ClassFile.ACC_SYNTHETIC)
                    .withInterfaceSymbols(cdLazySingleton);
            generatePublicNoArgConstructor(classBuilder);
            generateGetImplByIndy(classBuilder);
        });
        return MethodHandles.lookup().defineClass(classBytes);
    }

    private static void generatePublicNoArgConstructor(ClassBuilder clb) {
        clb.withMethodBody(
                ConstantDescs.INIT_NAME,
                ConstantDescs.MTD_void,
                ClassFile.ACC_PUBLIC,
                cob -> {
                    cob.aload(0);
                    cob.invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void);
                    cob.return_();
                });
    }

    private static void generateGetImplByIndy(ClassBuilder clb) {
        var bsmHandle = MethodHandleDesc.ofMethod(
                DirectMethodHandleDesc.Kind.STATIC,
                cdBsmProvider,
                "indyBootstrap",
                MethodTypeDesc.of(ConstantDescs.CD_CallSite, ConstantDescs.CD_MethodHandles_Lookup, ConstantDescs.CD_String, ConstantDescs.CD_MethodType)
        );

        var bsmCSD = DynamicCallSiteDesc.of(
                bsmHandle,
                "get",
                MethodTypeDesc.of(ConstantDescs.CD_Object));

        clb.withMethodBody(
                "get",
                MethodTypeDesc.of(ConstantDescs.CD_Object),
                ClassFile.ACC_PUBLIC,
                codeBuilder -> {
                    codeBuilder.invokedynamic(bsmCSD);
                    codeBuilder.areturn();
                }
        );
    }
    
    public static CallSite indyBootstrap(MethodHandles.Lookup lookup, String name, MethodType type) {
        Class<?> generatedClass = lookup.lookupClass();
        String className = generatedClass.getName();
        Supplier<?> supplier = supplierRegistry.get(className);
        if (supplier == null) {
            // 一般到不了这里
            throw new RuntimeException("Cannot find supplier for " + className);
        }
        Object inst = supplier.get();
        return new ConstantCallSite(MethodHandles.constant(type.returnType(), inst));
    }
}

这里其实还有一个问题,就是这里的indyBootstrap方法是有优化空间的,这里就留给大伙自行解决了,相信大家在知道indy的具体步骤之后,就可以很快的发现优化空间在哪,该怎么优化了。

最后再提一点,这一个只是一个小工具,仅供参考,大家可以参考这个去实现自己想要的工具。

相关推荐
浮游本尊几秒前
Java学习第12天 - Spring Security安全框架与JWT认证
java
David爱编程19 分钟前
happens-before 规则详解:JMM 中的有序性保障
java·后端
小张学习之旅20 分钟前
ConcurrentHashMap
java·后端
熊文豪1 小时前
保姆级Maven安装与配置教程(Windows版)
java·windows·maven·maven安装教程·maven配置教程·maven安装与配置教程
怀旧,2 小时前
【C++】 9. vector
java·c++·算法
渣哥2 小时前
震惊!Java注解背后的实现原理,竟然如此简单又高深!
java
hqxstudying2 小时前
JAVA限流方法
java·开发语言·安全·限流
shylyly_2 小时前
Linux->多线程2
java·linux·多线程·线程安全·线程同步·线程互斥·可重入
小蒜学长3 小时前
基于实例教学的软件工程专业教学系统
java·spring boot·后端·软件工程