当前位置: 首页 > 知识库问答 >
问题:

JVM的编译器是否使用“宽”goto?

鱼志学
2023-03-14

我想你们大多数人都知道goto在Java语言中是一个保留的关键字,但实际上并没有被使用。你们可能也知道goto是一个Java虚拟机(JVM)操作码。我认为Java、Scala和静态编程语言的所有复杂的控制流结构都是在JVM级别上使用gotoifeqiphoneiflt等的某种组合来实现的。

查看JVM规范https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w我看到还有一个goto_wopcode。而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_ws。但是既然它在规范中,应该是可以做到的。

我的问题是,是否有一个原因,编译器或其他生成器的字节码可能会使用goto_w与当前65535限制,而不是表明它可以做到?

共有3个答案

松俊才
2023-03-14

在一些编译器中(在1.6.0和11.0.7中尝试过),如果一个方法足够大,goto_w,它只使用goto_w。即使它有非常局部的跳转,它仍然使用goto_w。

楮庆
2023-03-14

当分支装入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
…
强志学
2023-03-14

方法代码的大小可以大到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字节码,您需要一个解释器(但我们有一个