10

考虑以下java代码:

public int main() {
    int i = 1111;

    for (; rules(i) != true && i < Integer.MAX_VALUE; i++) {
        //LOG.debug("Testing i: " + i);
    }

    System.out.println("The mystery number is: " + i);

    return i;
}

protected boolean rules(int nb) {
    //...
}

我发现即使 for 循环继续评估是true,当循环体为空时,循环也会停止执行。

的最终结果main是错误的(i大约 98% 的时间是 16698,有时会略高/略低)。

如果我从循环体中取消注释 LOG 语句,循环将继续运行,直到循环继续评估false

我使用的 JVM 是 MacOS X VM 1.6.0。

  • 它是否在进行某种运行时优化
  • 这个运行时优化可以被认为是一个错误吗?
  • 还是在 Java 规范中的某处说继续评估不应该运行功能操作?

ps:完整的代码源+它的单元测试可以在这里找到:https ://gist.github.com/dirtyhenry/5804130

更新:

  • 我只通过junit运行代码。junit 可以为这种行为负责吗?

更新 2:

java -version

返回:

java version "1.6.0_51"
Java(TM) SE Runtime Environment (build 1.6.0_51-b11-456-11M4508)
Java HotSpot(TM) 64-Bit Server VM (build 20.51-b01-456, mixed mode)
4

7 回答 7

3

我在这里遇到了类似的问题:近乎空的 Java For-Loop 行为很奇怪 您应该尝试使用 JVM 1.7 或尝试使用 while 循环:

public int main() {
    int i = 1111;

    while(i < Integer.MAX_VALUE){
        if (!rules(i)) {
            break;
        }
        i++
    }

    System.out.println("The mystery number is: " + i);

    return i;
}
于 2013-08-15T10:19:53.457 回答
2

我在 SO 上看到了一些关于循环优化的参考,仅在超过 10000 次迭代后才发生。也许这就是为什么“幻数”经常在 16000 左右?

这里有一个很好的讨论

用于优化循环语句的 JVM 选项

于 2013-06-18T12:12:38.617 回答
1

我用 jdk 1.7.0_21 测试了你的代码,它返回了相同的结果,942210,有或没有 LOG.debug 语句。

    int i = 0;
    for( ; !rules(i) && i < Integer.MAX_VALUE ; i++ ){
        //LOG.debug( "test " + i );
    }
    System.out.println( "i is " + i );

我还打印了两个版本的字节码(空循环)

   0: aload_0       
   1: invokespecial #3                  // Method java/lang/Object."<init>":()V
   4: iconst_0      
   5: istore_1      
   6: aload_0       
   7: iload_1       
   8: invokevirtual #4                  // Method rules:(I)Z
  11: ifne          26
  14: iload_1       
  15: ldc           #5                  // int 2147483647
  17: if_icmpge     26
  20: iinc          1, 1
  23: goto          6
  26: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
  29: new           #7                  // class java/lang/StringBuilder
  32: dup           
  33: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
  36: ldc           #9                  // String i is 
  38: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  41: iload_1       
  42: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  45: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  48: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  51: return 

带调试打印输出的循环

   0: aload_0       
   1: invokespecial #3                  // Method java/lang/Object."<init>":()V
   4: iconst_0      
   5: istore_1      
   6: aload_0       
   7: iload_1       
   8: invokevirtual #4                  // Method rules:(I)Z
  11: ifne          48
  14: iload_1       
  15: ldc           #5                  // int 2147483647
  17: if_icmpge     48
  20: new           #6                  // class java/lang/StringBuilder
  23: dup           
  24: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
  27: ldc           #8                  // String test 
  29: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  32: iload_1       
  33: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  36: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  39: invokestatic  #12                 // Method LOG.debug:(Ljava/lang/String;)V
  42: iinc          1, 1
  45: goto          6
  48: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
  51: new           #6                  // class java/lang/StringBuilder
  54: dup           
  55: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
  58: ldc           #14                 // String i is 
  60: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  63: iload_1       
  64: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  67: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  70: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  73: return 

如您所见,循环结构是相同的。20 - 39 只是构造字符串并调用 LOG.debug。

所以,这可能是一个jdk问题。尝试1.7,它应该可以工作。

于 2013-08-16T09:09:17.230 回答
0

您的另一个选择是在块内运行规则测试。JIT 编译器可能会抛出循环,因为它是空的。尝试这样的事情:

public int main() {
    int i = 1111;

    for (; i < Integer.MAX_VALUE; i++) {
        boolean completed = !rules(i);
        if (completed) {
            break;
        }
    }

    System.out.println("The mystery number is: " + i);

    return i;
}
于 2013-07-23T12:28:04.060 回答
0

什么都不做循环和条件可以被 JIT 编译器优化掉,这绝对是正在发生的事情。

于 2013-06-18T15:51:20.657 回答
0

在将其转换为常规的 Java/main 程序(即 public static void main( String[] args ),不要将“main”名称用于其他目的)之后,我已经尝试过了。我已将规则 (nb) 实施为“return false;” 并且它总是停在 2147483647。我也尝试过'return Math.random() < 0.01',它平均在 1200 左右停止,正如几何分布可以预测的那样。

我认为在您的情况下,规则()中发生了一些事情,javac 不能仅仅因为主体为空而跳过循环,您已经在可能想要遍历的条件下编写代码。我写这样的循环: while ( DORunThis() ); 一直运行,直到方法返回 false,如预期的那样。

顺便:

!rules ( i ) 比丑陋的更紧凑和优雅: rules( i ) != true

也许你真的不想要:i < Integer.MAX_VALUE,但是:

for (; !rules ( i ) && i != Integer.MAX_VALUE; i++ );

它也能够评估 MAX_VALUE 的 rules()。

于 2013-06-30T11:02:51.190 回答
0

尝试用任何其他(未注释的)有效语句替换日志记录语句,然后循环也会按预期运行。它绝对是 JVM 代码优化策略。我认为 JVM 没有理由无缘无故地在循环中循环。

于 2013-06-18T12:05:35.373 回答