问题:第14行是什么意思?
使用javap-v-c来反汇编以下代码:
public class test {
static int i = 2;
public static void main(String[] args) {
test x = new test();
System.out.println("text + String: " + i);
}
}
在主函数中,我们得到以下内容:
14: invokedynamic #20, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
19: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
...
BootstrapMethods:
0: #38 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#44 text + String: u0001
因此,例如,第19行表示从运行时常量池中的#24项调用虚拟函数。调用的方法是java/io/PrintStream
类中的println()
,其输入来自Ljava/lang/String
类,其返回值为Void。
至于第14行,#0保存对BootstrapMethod的引用,并返回一个类为CallSite
的Object,对吗?然后:
- #20指的是什么
- 注释
#0:makeConcatWithConstants:(I)Ljava/lang/String;
是什么意思
此外,我在哪里可以找到更多关于Javap反汇编代码语法的信息?或者什么是正确的关键字?Oracle关于the JVM instruction set
的文档似乎没有明确描述该评论的含义。
简短版本:Java从Java 9开始就使用invokedynamic来连接字符串。
让我们把它分解一下:
Invokedynamic有两个步骤:
- 首次调用指令时,将调用引导程序方法。当它返回时,调用站点将链接到引导方法的结果
- 后续调用将直接调用目标MethodHandle
CallSite只是该MethodHandle的持有者。根据所使用的CallSite
子类,该网站可能稍后会重新链接。
如果我们看一下说明书,我们会在末尾看到以下内容:
#0:makeConcatWithConstants:(I)Ljava/lang/String;
第一部分(#0
(的意思是:引导方法#0。
第二部分是名称-它被传递给引导方法,可能在那里使用,也可能不在那里使用
第三部分是生成目标的方法类型。在我们的例子中:一个接受int
并返回java.lang.String
的方法。
如果我们现在看一下引导方法#0,我们会看到一个方法引用,这里是StringConcatFactory.makeConcatWithConstants(…(。我们还看到还有一个额外的参数:字符串"text + String: u0001"
。
bootstrap方法现在的任务是返回一个MethodHandle(在CallSite内(,在这种情况下,它会进行字符串串联。但是它如何进行字符串串联(StringBuilder、string.format、字节码旋转、链接MethodHandles…(对实际类来说并不重要。它只想将字符串连接起来。
让我们试着用手模仿这种行为。毕竟,bootstrap方法是一种普通的Java方法:
public static void main(String[] args) throws Throwable {
CallSite cs = StringConcatFactory.makeConcatWithConstants(MethodHandles.lookup(),
"makeConcatWithConstants", MethodType.methodType(String.class, int.class),
"text + String: u0001");
int x = 2;
String result = (String) cs.dynamicInvoker().invokeExact(x);
System.out.println(result);
x = 3;
result = (String) cs.dynamicInvoker().invokeExact(x);
System.out.println(result);
}
(虚拟机做了更多的事情,比如它会记住结果,不会再调用引导方法,但对于我们的小示例来说,这已经足够好了(。
在这一点上,我们可以看看引导方法是如何完成它的工作的
事实证明:您可以配置虚拟机以使用不同的策略
它使用它在java.base
中的特权位置来访问java.lang.String的包私有构造函数,该构造函数不复制数组-如果之后不修改内容,这是安全的。
默认策略是MethodHandle链接。
好消息是:如果有人在某个时候编写了一个更好的策略,您的程序将从中受益,而无需重新编译。
参考JVM规范:
首先,无符号indexbyte1和indexbyte2用于构造到当前类的运行时常量池的索引(§2.6(,…索引处的运行时常数池条目必须是对动态计算调用站点的符号引用(§5.1(
方便的是,javap
已经查找了常量池并对信息进行了解码;结果是像行中的指令后面的注释一样打印的内容
14: invokedynamic #20, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
编号#0
是您已过帐的BootstrapMethods
属性的索引。方法名称的含义取决于引导程序方法。此外,还有类型描述符(I)Ljava/lang/String;
,因此此特定调用使用int
并生成String
。
运行时会发生什么取决于引用的引导程序方法。此调用引用了static
方法StringConcatFactory.makeConcatWithConstants(...)
,部分方法中,字符串串联是用Java 9编译的。
该方法的文档告诉我们,invokedynamic
指令中使用的方法名称无关紧要,BootstrapMethod
属性的静态参数,即text + String: u0001
决定了字符串格式。u0001
是"普通参数"的占位符,即int
参数。