绕过Java JIT Bug

3

我们的Java环境似乎受到了奇怪的错误影响。

我们已经遇到了两次同样的“不可能发生”的异常;其中一个问题在运行过程中出现了42,551次,持续了48分钟,然后自动清除。

代码失败是因为这一行触发的:

return String.format("%1d%XY%d", source, System.currentTimeMillis(), quoteID);

假设int source = 0long quoteID = 44386874(例如),则异常情况为:

java.util.UnknownFormatConversionException: Conversion = 'd'
        at java.util.Formatter$FormatSpecifier.conversion(Formatter.java:2605)
        at java.util.Formatter$FormatSpecifier.<init>(Formatter.java:2633)
        at java.util.Formatter.parse(Formatter.java:2479)
        at java.util.Formatter.format(Formatter.java:2413)
        at java.util.Formatter.format(Formatter.java:2366)
        at java.lang.String.format(String.java:2770)

检查代码'd'不应该引发此异常。

我们想到的最好解释是JIT编译器正在生成错误的字节码,但在随后的重新JIT时会写入良好的代码。

有人有解决/诊断此类问题的经验吗?

罗杰。


3
哪个JVM?您是否尝试将包含该行的方法标记为“final”,以更改JIT处理方式? - omerkudat
你尝试过显式索引来看是否有帮助吗?'X' 格式化为十六进制数,而 'Y' 格式化为年份,是这样吗? - ChadNC
如果你认为这是一个合法的错误,那么你应该向Sun提交一些内容,以便他们可以调查并(可能)修复它。 - matt b
1
你应该在一开始就提到你正在使用JRockit。这会增加JIT错误的可能性。 - Michael Myers
6个回答

7

我怀疑这不是一个真正的JIT问题。

你是否排除了其他可能性,比如内存损坏或运行时环境问题?

你是怎么得出这是一个JIT问题的结论的?

只是为了让你放心,这是抛出异常的代码:

private char java.util.Formatter.FormatSpecifier.conversion(String s) {
    c = s.charAt(0);
    if (!dt) {
    if (!Conversion.isValid(c))
        throw new UnknownFormatConversionException(String.valueOf(c));

        ///////..........
}

使用:

static boolean java.util.Formatter.Conversion.isValid(char c) {
    return (isGeneral(c) || isInteger(c) || isFloat(c) || isText(c)
        || c == 't' || c == 'c');
}

d是一个合法的整数标识符,而isValid()应该返回True

调试这个问题不是难点,有教养的猜测会说你什么也找不到。这显然是一个内存损坏/环境问题。听起来这个问题很容易复现。尝试在不同的机器、不同的操作系统、不同的JVM上进行测试。

我的直觉——你的问题不是JIT编译器造成的。


不,我们没有排除任何可能性。鉴于故障的性质(以及程序是纯Java),JIT错误是我们迄今为止最有可能的猜测。我们可能有一个JVM内存错误,但它自动清除的事实似乎符合jit问题。 - Roger Orr
2
实际上,它是零星的和非常奇怪的事实符合它是硬件问题的事实。JIT编译器、JVM环境中的错误虽然可能存在,但极不可能发生。 - Reverend Gonzo

2

测试内存!例如使用memtest86+。它可以在Ubuntu CD引导菜单中找到。


1

检查是否为JIT错误的简单方法是将您的代码放入循环中。如果是JIT错误,则会在循环内部失败但在外部不会:

for (int i = 0; i < 100000; i++) {
        String.format("%1d%XY%d", source, System.currentTimeMillis(), quoteID);
    }

很遗憾,它似乎并不那么一致 - 如果这是一个即时编译器的错误,那么它看起来是一个与执行路径相关的错误之一。 - Roger Orr
这是 JIT 工作原理的过于简单化的模型。JIT 在编译、内联以及何时进行这些操作方面做出了非常复杂的决策。 - MK.

1
你确定代码中的"d"是ASCII字符而不是某个看起来像"d"的Unicode字符吗?
(可能性很小,但奇怪的事情总会发生。)

0

执行:

final long time;

time = System.currentTimeMillis();

try
{
    return String.format("%1d%XY%d", source, time, quoteID);
}
catch(final UnknownFormatConversionExceptio ex)
{
    // log the source
    // log the time
    // log the quoteID
    throw ex;
}

如果看起来没有什么异常,那么请查看String.format的源代码,并手动跟踪这些值(听起来您可能已经进行了跟踪,但使用实际值进行跟踪可能会有所帮助)。
正如其他人所说,这里也可能存在随机内存错误。如果可能,请在计算机上运行内存检查并查看结果(我曾经通过重新插入内存解决过问题...)。

唉,我们已经尝试使用已知的错误值[可从进程的完整日志文件中获取]运行它,但未能重现故障。请注意,鉴于调用堆栈的位置,当问题发生时,我们正在创建FormatSpecifier的代码中,查看(固定)格式字符串“%1d%XY%d”。 - Roger Orr

0
我会先检查 java.util.Formatter 的源代码,看看是否还有其他检查,导致 'd' 模式字符引发异常。你使用的是哪个 Java 版本?

我们已经检查了源代码,正在使用jrockit R27.6.0-50_o-100423-1.6.0_05-20080626-2104-linux-x86_64。Formatter.java的1.6源代码在Conversion.isValid(c)失败时会抛出异常,当isInteger(char c)返回true时,这应该返回true,因为'd'是DECIMAL_INTEGER。如果它是不正确的格式代码,那么代码一开始能够工作,然后停止,最后重新开始是很奇怪的! - Roger Orr
你有jrockit的源代码吗?那不是Oracle的专有JVM吗? - jitter
不,我们有Java类的源代码,并且行号似乎与我们得到的调用堆栈匹配 :-) - Roger Orr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接