【问题标题】:java throws impossible NullPointerExceptionjava抛出不可能的NullPointerException
【发布时间】:2014-05-05 14:14:41
【问题描述】:

精简到要领我有以下代码:

public class C_Test extends A_Test {
    C_Test() {
        super( null );
    }
}

public abstract class A_Test {
    private final String m_test;

    public A_Test( String test ) {
        m_test = C_ObjectUtil.defaultIfNull( test, "" );

        if( m_test == null ) {
            throw new RuntimeException( "it happened again!" );
        }
    }
}

public class C_ObjectUtil {
    public static <T> T defaultIfNull( T object, T defaultObject ) {
        return ( object == null ) ? defaultObject : object;
    }
}

使用 JDBC 从 DB2 数据库读取大约 70 MB 数据后,调用 new C_Test() 会引发不可能的异常。到目前为止,构造函数已被调用超过 100000 次。

在将方法defaultIfNull 内联到构造函数A_Test(这将删除泛型)之后,一切正常,没有任何错误!

环境:Sun JDK 1.7.0_25 64Bit, Linux

知道这里发生了什么吗?


堆栈跟踪来了。它与我上面显示的简化版本不匹配。这里的异常是在构造函数A_Test调用m_test.equals( "null" )时抛出的。

java.lang.NullPointerException
        at com.src.db.attribvisitors.A_AttribVisitorAssignFromResultSet.<init>(A_AttribVisitorAssignFromResultSet.java:54)
        at com.src.db.attribvisitors.C_AttribVisitorAssignFromSelectStatement.<init>(C_AttribVisitorAssignFromSelectStatement.java:37)
        at com.src.db.attribvisitors.C_AttribVisitorAssignFromSelectStatement.<init>(C_AttribVisitorAssignFromSelectStatement.java:43)
        at com.src.framework.db.migration.A_DbTableRow.initFromResultset(A_DbTableRow.java:96)
        at com.src.framework.db.migration.helper.C_DbTableProcessor$1.process(C_DbTableProcessor.java:66)
        at com.src.db.C_TxSql.query(C_TxSql.java:144)
        at com.src.db.C_SqlExecuter$3.execute(C_SqlExecuter.java:142)
        at com.src.db.C_SqlExecuter.execute(C_SqlExecuter.java:80)
        at com.src.db.C_SqlExecuter.query(C_SqlExecuter.java:138)
        at com.src.framework.db.migration.helper.C_DbTableProcessor.processTable(C_DbTableProcessor.java:58)
        at com.src.framework.db.migration.helper.C_DbTableProcessor.processTable(C_DbTableProcessor.java:39)
        at com.src.tools.C_DbExporter.exportTable(C_DbExporter.java:93)
        at com.src.tools.C_DbExporter.exportTables(C_DbExporter.java:77)
        at com.src.tools.C_DbExporter.exportTables(C_DbExporter.java:61)
        at com.src.tools.C_DbExporterAndImporter.exportTables(C_DbExporterAndImporter.java:95)
        at com.src.tools.C_DbExporterAndImporter.doMain(C_DbExporterAndImporter.java:83)
        at com.src.common.C_MainUtil.runMain(C_MainUtil.java:150)
        at com.src.tools.C_DbExporterAndImporter.main(C_DbExporterAndImporter.java:57)

【问题讨论】:

  • 这类“不可能的事情”的问题在于,当您“将代码简化为必需品”时,您就摆脱了实际的错误。当然,这可能是 JIT 中的一个错误,但我们不要妄下结论。您可以尝试使用不同的 JVM 吗?
  • 在您发布“简化为基本要素”的代码之前,您应该测试您发布的示例是否有效...如果有效,则错误在其他地方,您应该继续搜索.
  • 真正的代码要大到贴出来。但我已将其降低到可能很重要的最低环境。
  • @tangens 此应用程序中是否有多个线程?你有可能从你的构造函数中泄露这个吗?
  • @tangens 我的建议是,以这个“基本”示例为例,然后开始构建您当前拥有的内容。在这里和那里之间的某个地方,将引入一个错误。如果那个时候你不明白这个bug,那么你可以来发帖

标签: java generics jdbc nullpointerexception


【解决方案1】:

如果没有您没有向我们展示的任何外部干预,这是不可能的。

这个调用

m_test = C_ObjectUtil.defaultIfNull( test, "" );

test 持有的值和对String 文字"" 的引用的值推入堆栈。调用该方法,使用这两个引用。然后调用这个

public static <T> T defaultIfNull( T object, T defaultObject ) {
    return ( object == null ) ? defaultObject : object;
}

来自test 的值,即。 null,绑定到object,对String 的引用值绑定到defaultObject。执行条件运算符并返回defaultObject,即。非空String。在调用代码中分配给m_test。没有任何东西可以拦截代码中的这些步骤。不是另一个线程,不是任何代理机制(它是直接调用的static 方法),不是我能想到的任何东西。

你的错误在别处。

(如果您发布有助于确定真正问题的新详细信息,我将删除或编辑此答案。)

【讨论】:

  • 感谢您的回答。但我真的不知道在哪里寻找错误。如果我内联方法defaultIfNull,它就会消失,如果我不这样做,它会再次出现。
  • @tangens 除了告诉你这是不可能的(失败的错误,但我对此表示怀疑)之外,我无法猜测发生了什么。 IMO,你必须找到一种方法来复制它并向我们展示。
  • @tangens C_ObjectUtil 是否有一个其他类的名称,而您正在导入错误的名称? (暗中刺伤)
  • @Cruncher 好主意,但我没有发现任何冲突 :-(
  • @tangens 你试过给方法提供具体的参数和返回类型吗?
【解决方案2】:

你所描述的不太可能。然而,这不会是第一次发生的可能性很小。我有点犹豫是否将其发布为答案,因为它确实不是一个,但我们开始吧。

作为 Kayaman points out,所描述的行为可能是由于不正确的 JIT 优化造成的。为了验证这个理论,我将执行两个步骤。

  1. 打开-XX:+PrintCompilation 并在不祥的异常发生之前监视相关方法的编译输出。 这本身并不能证明 JIT 编译器正在应用不正确的优化。但是,开始第二步并查看 JIT 生成的机器代码就足够了。

  2. 打开-XX:+PrintAssembly 并检查相关方法的编译器输出的机器代码。这是很多工作,尤其是当您是第一次这样做时。

在我看来,有三种可能。

  • 这是您代码中的一个(细微)错误 — 与 JIT 无关。
  • JIT 实际上是错误的(不太可能,抱歉)。您应该能够在上面的步骤 (2) 中证明这一点。
  • JIT 是正确的,但它的(有效)优化会触发一个原本处于休眠状态的错误。您很有可能在上面的第 (2) 步中找到它。

最后要指出的一件事——Cruncher 已经alluded——是竞争条件的可能性。比赛条件值得特别提及,因为它们介于第一类和第三类之间。它们可能与优化本身无关,但优化的加速可能会导致不需要的和无法实现的执行跟踪。

祝你在狩猎中好运。 :)

【讨论】:

  • 感谢您的精彩回答。那是很重的东西!我不知道我是否有时间研究这个。但我认为这将是缩小错误范围的正确方法。
【解决方案3】:

我从 Sun JDK 1.7.0_25 64Bit 切换到 Sun JDK 1.7.0_55 64Bit 并且 NPE 消失了!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-12
    • 2015-12-30
    • 2021-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-24
    相关资源
    最近更新 更多