【发布时间】:2015-10-07 14:13:32
【问题描述】:
我在运行 Java 8 时遇到了一个相当奇怪的问题。这个问题本身就好像 JVM 本身发生了某种计时错误一样。它本质上是间歇性的,但很容易重现(至少在我的测试环境中)。问题是在某些情况下,显式设置的数组值被破坏并替换为 0.0。具体来说,在下面的代码中,array[0] 在行 new Double(r.nextDouble()); 之后计算为 0.0。然后,如果您立即再次查看array[0] 的内容,它现在显示的值是正确的值 1.0。运行此测试用例的示例输出是:
claims array[0] != 1.0....array[0] = 1.0
claims array[0] now == 1.0...array[0] = 1.0`
我正在运行 64 位 Windows 7,并且能够在 Eclipse 中以及使用 JDK 1.8_45、1.8_51 和 1.8_60 从命令行编译时重现此问题。我无法产生运行 1.7_51 的问题。在另一个 64 位 Windows 7 机器上也证明了相同的结果。
这个问题出现在一个大型的、非平凡的软件中,但我已经设法将它浓缩为几行代码。下面是一个演示该问题的小测试用例。这是一个看起来很奇怪的测试用例,但似乎都是导致错误所必需的。不需要使用Random - 我可以用任何双精度值替换所有r.nextDouble() 并演示问题。有趣的是,如果将someArray[0] = .45; 替换为someArray[0] = r.nextDouble();,我无法复制该问题(尽管.45 没有什么特别之处)。 Eclipse 调试也无济于事——它改变了足够多的时间,以至于它不再发生。即使是放置得当的System.err.println() 语句也会导致问题不再出现。
同样,问题是间歇性的,因此要重现该问题,可能需要多次运行此测试用例。我认为在得到上面显示的输出之前,我最多需要运行大约 10 次。在 Eclipse 中,我在运行后给它一两秒钟,然后如果它没有发生就杀死它。从命令行相同 - 运行它,如果它没有发生 CTRL+C 退出并重试。看来,如果它会发生,它会很快发生。
我过去遇到过类似的问题,但它们都是线程问题。我不知道这里发生了什么——我什至看过字节码(顺便说一下,字节码在 1.7_51 和 1.8_45 之间是相同的)。
对这里发生的事情有什么想法吗?
import java.util.Random;
public class Test {
Test(){
double array[] = new double[1];
Random r = new Random();
while(true){
double someArray[] = new double[1];
double someArray2 [] = new double [2];
for(int i = 0; i < someArray2.length; i++) {
someArray2[i] = r.nextDouble();
}
// for whatever reason, using r.nextDouble() here doesn't seem
// to show the problem, but the # you use doesn't seem to matter either...
someArray[0] = .45;
array[0] = 1.0;
// commented out lines also demonstrate problem
new Double(r.nextDouble());
// new Float(r.nextDouble();
// double d = new Double(.1) * new Double(.3);
// double d = new Double(.1) / new Double(.3);
// double d = new Double(.1) + new Double(.3);
// double d = new Double(.1) - new Double(.3);
if(array[0] != 1.0){
System.err.println("claims array[0] != 1.0....array[0] = " + array[0]);
if(array[0] != 1.0){
System.err.println("claims array[0] still != 1.0...array[0] = " + array[0]);
}else {
System.err.println("claims array[0] now == 1.0...array[0] = " + array[0]);
}
System.exit(0);
}else if(r.nextBoolean()){
array = new double[1];
}
}
}
public static void main(String[] args) {
new Test();
}
}
【问题讨论】:
-
我无法重现这个。在这里按预期工作。
-
如果您可以重现此问题,我建议您针对 JDK 提交错误。我只能猜测它与 JIT 的加入有关。出于兴趣:需要
new Double(...)吗?这是我不希望在实际代码中找到的东西。 @PM77-1:将1.0的整数值存储在double中绝不会导致此类问题,因为它可以在不损失精度的情况下表示。如果==给出错误结果,我宁愿期望由于某种原因使用了一个装箱值(由于 JVM 中的错误)。 -
我可以重现这个(oracle java 1.8.0_60 on 64-bit linux machine)。
-
从生产代码到这个小例子,追查问题一定是做了大量工作……
-
这绝对是一个JIT优化bug。
-XX:-TieredCompilation或-XX:-EliminateAllocations可能是可接受的解决方法,不会显着降低性能。
标签: java memory jvm java-8 timing