我想你们大多数人都知道goto
在Java语言中是一个保留的关键字,但实际上并没有被使用。你们可能也知道goto
是一个Java虚拟机(JVM)操作码。我认为Java、Scala和静态编程语言的所有复杂的控制流结构都是在JVM级别上使用goto
和ifeq
、iphone
、iflt
等的某种组合来实现的。
查看JVM规范https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w我看到还有一个goto_w
opcode。而goto
采用2字节的分支偏移量,goto_w
采用4字节的分支偏移量。说明书上说
尽管goto_w指令采用4字节的分支偏移量,但其他因素将方法的大小限制为65535字节(§4.11)。这个限制可能会在Java虚拟机的未来版本中提高。
在我看来,goto_w
就像其他一些*_w
操作码一样,是未来的证明。但我也想到,也许可以使用goto_w
将两个更重要的字节清零,将两个不太重要的字节与goto
相同,并根据需要进行调整。
例如,给定此JavaSwitch-Case(或Scala Match-Case):
12: lookupswitch {
112785: 48 // case "red"
3027034: 76 // case "green"
98619139: 62 // case "blue"
default: 87
}
48: aload_2
49: ldc #17 // String red
51: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
54: ifeq 87
57: iconst_0
58: istore_3
59: goto 87
62: aload_2
63: ldc #19 // String green
65: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
68: ifeq 87
71: iconst_1
72: istore_3
73: goto 87
76: aload_2
77: ldc #20 // String blue
79: invokevirtual #18
// etc.
我们可以把它改写成
12: lookupswitch {
112785: 48
3027034: 78
98619139: 64
default: 91
}
48: aload_2
49: ldc #17 // String red
51: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
54: ifeq 91 // 00 5B
57: iconst_0
58: istore_3
59: goto_w 91 // 00 00 00 5B
64: aload_2
65: ldc #19 // String green
67: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 91
73: iconst_1
74: istore_3
75: goto_w 91
79: aload_2
81: ldc #20 // String blue
83: invokevirtual #18
// etc.
我还没有真正尝试过这个,因为我可能犯了一个错误,改变了“行号”来适应goto_w
s。但是既然它在规范中,应该是可以做到的。
我的问题是,是否有一个原因,编译器或其他生成器的字节码可能会使用goto_w
与当前65535限制,而不是表明它可以做到?
在一些编译器中(在1.6.0和11.0.7中尝试过),如果一个方法足够大,goto_w,它只使用goto_w。即使它有非常局部的跳转,它仍然使用goto_w。
当分支装入goto
时,没有理由使用goto_w
。但您似乎忽略了使用有符号偏移量时分支是相对的,因为分支也可以向后移动。
在查看像javap
这样的工具的输出时,您不会注意到它,因为它会在打印之前计算出最终的绝对目标地址。
所以goto
的范围是-327678…32767
并不总是足以解决0…65535
范围内的每个可能目标位置。
例如,以下方法在开始时将有一个goto_w
指令:
public static void methodWithLargeJump(int i) {
for(; i == 0;) {
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
} } } } } } } } } } } } } } } } } } } }
}
}
static void x() {}
Ideone上的演示
Compiled from "Main.java"
class LargeJump {
public static void methodWithLargeJump(int);
Code:
0: iload_0
1: ifeq 9
4: goto_w 57567
…
方法代码的大小可以大到64K。
shortgoto
的分支偏移量是一个有符号的16位整数:从-32768到32767。
因此,短偏移量不足以使65K方法从开始跳到结束。
甚至javac
有时也会发出goto_w
。下面是一个例子:
public class WideGoto {
public static void main(String[] args) {
for (int i = 0; i < 1_000_000_000; ) {
i += 123456;
// ... repeat 10K times ...
}
}
}
用javap-c
反编译:
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2
5: if_icmplt 13
8: goto_w 50018 // <<< Here it is! A jump to the end of the loop
...
问题内容: 我经常碰到声称Java被解释的文章。我知道Oracle的HotSpot JRE提供即时编译,但是大多数台式机用户都是这种情况吗?例如,如果我通过http://www.java.com/en/download下载Java ,它将包括JIT编译器吗? 问题答案: 是的,一点没错。声称Java被解释的文章通常由不了解Java的工作原理或不了解解释的含义的人撰写。 话虽如此,HotSpot 有
问题内容: 我对JVM有一个非常基本的问题:它是编译器还是解释器? 如果它是解释器,那么JVM内部存在的JIT编译器怎么办? 如果两者都不是,那么JVM到底是什么?(我不希望将字节码转换为机器特定的代码等jVM的基本定义。) 问题答案: 首先,让我们对以下术语有一个清晰的认识 是Java编译器-将Java代码编译为 Bytecode 是Java虚拟机-运行/解释/将字节码转换为本 机代码 是即时编
在Google的Python类中 Python是一种动态的解释(字节码编译)语言 我知道什么是解释器,也知道什么是字节码,但两者加在一起似乎不合适。在阅读了一些之后,我变得更清楚了,基本上Python源代码在被解释之前是自动编译的;但是出现了一些新的问题。 使用Python解释器时,不会发生编译吗?如果有,什么时候?例如,如果您只是在命令行中键入代码,并且每次按enter键时它都会运行,那么编译器
我在HashMap中存储的对象作为键覆盖equals(),但不是hashCode();当我在映射中放置一个对象时,equals()方法没有被调用。如果我还重写hashCode(),则会调用equals()方法。为什么? 为什么我不能使用自定义的equals方法来阻止向映射中添加对象,而不管我是否重写hashCode()? 谢谢 如果注释了hashCode(),则大小为2,否则大小为1。 我在想,如
问题内容: 可以说,我的Java程序的瓶颈确实是一些紧密的循环,无法计算一堆矢量点积。是的,我已经进行了概要分析,是的,它是瓶颈,是的,它很重要,是的,这就是算法的方式,是的,我运行了Proguard来优化字节码,等等。 实质上,这是点产品。与之类似,我有两个,我需要计算成对乘积之和。我知道处理器指令集可以像SSE或MMX一样快速且批量地执行此类操作。 是的,我可能可以通过在JNI中编写一些本机代
我试图理解Java源代码是如何执行的,我对JVM中的JIT编译器究竟是什么感到困惑。首先,让我告诉您我是如何理解从Java源代码到在计算机上执行机器代码的过程的。也许,我在这一过程中误解了一些导致混淆的东西。 步骤如下: 源代码被编译成字节码(.class文件) 现在,根据维基百科关于JVM的文章,更具体地说是“字节码解释器和实时编译器”部分,为了执行Java字节码,您需要一个解释器(但我们有一个