29

我一直想知道为什么 JVM 在抛出 a 时不告诉你哪个指针(或更准确地说,哪个变量)为空NullPointerException

行号不够具体,因为有问题的行通常可能包含许多可能导致错误的变量。

是否有任何编译器或 JVM 标志可以使这些异常消息更有用?

4

7 回答 7

51

这是因为取消引用总是在没有可用名称时发生。该值被加载到操作数堆栈中,然后被传递给取消引用它的 JRE 操作码之一。但是,操作数堆栈没有与空值相关联的名称。它只有“空”。使用一些巧妙的运行时跟踪代码,可以派生名称,但这会增加开销,但价值有限。

因此,没有 JRE 选项可以打开空指针异常的额外信息。

在此示例中,引用存储在本地插槽 1 中,该插槽映射到本地变量名称。但是取消引用发生在 invokevirtual 指令中,它只在堆栈上看到一个“null”值,然后抛出一个异常:

15 aload_1
16 invokevirtual #5 

同样有效的是数组加载后跟取消引用,但在这种情况下,没有名称可以映射到“空”值,只是另一个值的索引。

76 aload    5
78 iconst_0
79 aaload
80 invokevirtual #5

您也不能将名称静态分配给每条指令 - 此示例生成大量字节码,但您可以看到取消引用指令将接收 objA 或 objB,您需要动态跟踪以报告正确的,因为两个变量都流向相同的取消引用指令:

(myflag ? objA : objB).toString()
于 2009-07-22T22:03:38.103 回答
12

一旦你 JIT 代码,它只是本机指针数学,如果本机代码中的任何指针为空,它就会引发异常。将该程序集反转回原始变量会产生毁灭性的性能影响,并且考虑到 JIT 将生成的代码优化到不同的级别,这通常甚至是不可能的。

于 2009-07-22T21:09:48.523 回答
2

从 Java 15 开始,它终于告诉你了!请参阅此 JEP:https ://openjdk.java.net/jeps/358 (它实际上是在 Java 14 中添加的,但默认情况下被禁用,现在在 Java 15 中默认启用)。

于 2020-10-28T07:13:33.083 回答
0

如果您将行分成多行而不是在一行上进行多个方法调用,或者如果您在该行上设置断点并使用调试器单步执行该行,您可以很容易地找出哪个引用为空。

于 2009-07-22T21:14:24.317 回答
0

如果

行号不够具体,因为有问题的行通常可能包含许多可能导致错误的变量。

那么我建议:

  1. 将该行分成多行并将可能的NullPointerException生成值分配给临时变量。
  2. 使用调试器并单步执行每个方法调用,直到找到导致问题的方法调用。
于 2009-07-22T21:15:47.653 回答
0

不幸的是,这正是 Java 的工作方式。

如果这是“你的”代码,那么只需添加如下代码片段

if (foo == null) {
  throw new NullPointerException("foo == null");
}

在分配 foo 之后。如果 foo 是一个参数,则在方法体的开头立即检查,并抛出 IllegalArgumentException。

这应该可以帮助您澄清问题。

于 2009-07-22T21:59:49.933 回答
0

您可以在调试时在Eclipse中对空指针异常添加断点,以获取异常的确切原因。

于 2009-08-14T13:42:27.473 回答