【问题标题】:In java, "5/0" statement doesn't fire SIGFPE signal on my Linux machine, why?在 java 中,“5/0”语句不会在我的 Linux 机器上触发 SIGFPE 信号,为什么?
【发布时间】:2012-01-02 18:13:12
【问题描述】:

我写了一个很简单的c程序:

#include<stdio.h>

int main(){
    int a=2;
    int b=0;
    printf("%d\n", a/b);
}

并使用 strace 运行它: strace ./a.out 并得到以下输出(仅粘贴尾部)

... ...
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f04c7fb8000, 4096, PROT_READ) = 0
munmap(0x7f04c7f96000, 127640)          = 0
--- SIGFPE (Floating point exception) @ 0 (0) ---
+++ killed by SIGFPE +++
Floating point exception

输出符合我的预期,因为它被 SIGFPE 信号杀死了。

但是,用Java编写的同一个程序,没有收到SIGFPE信号,有人知道java如何处理“除以零”异常吗?

public class Main {

  public static void main(String[] args) {
    int a = 2;
    int b = 0;
    System.out.println(a / b);
  }
}

strace java -Xcomp Main

... ...
mprotect(0xf6949000, 8171520, PROT_READ|PROT_WRITE) = 0
mprotect(0xf6949000, 8171520, PROT_READ|PROT_EXEC) = 0
munmap(0xf774f000, 5727)                = 0
mmap2(NULL, 331776, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0xfffffffff68d0000
mprotect(0xf68d0000, 4096, PROT_NONE)   = 0
clone(child_stack=0xf6920494, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xf6920bd8, tls=0xf6920bd8, child_tidptr=0xff9c5520) = 958
futex(0xf6920bd8, FUTEX_WAIT, 958, NULL) = 0
exit_group(0)  

【问题讨论】:

  • 你得到了 Java 级别的异常,对吧? DivisionByZeroException 什么的。这还不够好吗?您对 JVM 如何与处理器对话感兴趣吗?
  • 也许我遗漏了一些东西,但你不会抓住 Java 中的 DivideByZeroException 吗?
  • 你为什么希望它们是一样的?这两种语言没有任何共同之处。
  • 我认为他是在问虚拟机如何在内部完成它,而不是如何在 Java 代码中捕获它。
  • 是的,正如 Boann 所说,我对 VM 内部的工作方式很感兴趣。 ~

标签: java c linux exception-handling signals


【解决方案1】:

在这里,它引发了一个 SIGFPE。

您忘记告诉strace 关注孩子。将-f 选项添加到strace,您应该会看到类似于:

[pid  2304] read(3, "\312\376\272\276\0\0\0001\0n\n\0\23\0I\t\0\3\0J\7\0K\n\0L\0M\n\0N\0"..., 2369) = 2369
[pid  2304] --- SIGFPE (Floating point exception) @ 0 (0) ---
[pid  2304] rt_sigreturn(0x1c50800)     = 5
[pid  2304] write(2, "Exception in thread \"main\" ", 27Exception in thread "main" ) = 27
[pid  2304] write(2, "java.lang.ArithmeticException: /"..., 40java.lang.ArithmeticException: / by zero) = 40
[pid  2304] write(2, "\n", 1

【讨论】:

    【解决方案2】:

    显然这是因为 JVM 在它的代码中有这样的东西:

    #include <stdio.h>
    #include <signal.h>
    #include <stdlib.h>
    
    void fpe_handler(int signum) {
      printf("JVM throws an ArithmeticException here...\n");
      exit (1);
    }
    
    int main() {
      int a = 5;
      int b = 0;
      signal(SIGFPE, fpe_handler);
      printf("%d\n", a / b);
      return 0;
    }
    

    JVM 还运行多个线程(请参阅上面日志中的 clone() 或在 java 运行时执行 ps -eLf),因此 strace 输出只是不完整的。

    如果更详细一点,未处理的 SIGFPE 表示程序中的错误,它发生在其中。如果 java 会被 SIGFPE 杀死,这将表明错误是在 JVM 中,而不是在您的应用程序中,在 JVM 中运行。

    【讨论】:

      【解决方案3】:

      与(普通)C 编译程序相比,Java 程序在运行时运行,而不是在处理器上运行,并且不依赖于平台。在java中除以零会触发这样的ArithmeticException:

      Exception in thread "main" java.lang.ArithmeticException: / by zero
      

      来自JLS

      由于以下三个原因之一引发异常:

      异常执行 Java 虚拟机同步检测到条件。这样的 条件的出现是因为:

      表达式的计算违反了 语言的正常语义,例如整数除以零, 如 §15.6 中所述

      加载或链接部分内容时出错 程序(§12.2、§12.3)

      超出了对资源的某些限制, 比如使用太多内存

      【讨论】:

      • 嗯.. 虽然我们说 Java 程序在运行时上运行,但它最终在处理器上运行。此外,我添加了参数“-Xcomp”,这意味着 JIT 必须将每个方法编译成本地处理器指令。此外,这两个程序都运行在 linux 之上,所以我希望它们都会引发 SIGFPE 信号。
      • 确实是这样,不过如果我没记错的话,这样的事情在执行前是由运行时检查的。
      • 在执行前检查除数是否为 0 成本很高,我认为 VM 不会这样做。
      • 对于这个特定的示例代码,如果编译器将其“优化”为“抛出新的 ArithmeticException”语句,我不会感到惊讶。 ;)
      • @zhangfaen:请参阅 Stein 在上面的评论。尝试除以您从 args 读取的变量。
      【解决方案4】:

      VM 可能会在模拟字节码中手动测试除数为 0(为了实现简单性),但为了提高性能,仍会切换到在 JIT 代码中检测 SIGFPE 信号。尝试将除法代码放在自己的子程序中,并在循环中调用它数千次以确保它被编译。

      【讨论】:

      • 试过了,但仍然没有发生 SIGFPE。恕我直言,不要认为 VM 每次都测试除数为 0,这显然是浪费。
      • @zhangfaen:尝试使用-XX:+PrintOptoAssembly模式。见:stackoverflow.com/questions/1551781/…
      • 这不是答案。
      【解决方案5】:

      Java 在虚拟机(JVM)中运行,它从程序中抽象出硬件。 Java 程序中的大多数错误都会导致 Java 异常,并且不会触发任何本机 cpu 或 os 错误代码。我猜这段代码会抛出 ArithmeticException(或类似的东西)。

      【讨论】:

      • 是的,它会抛出 ArithmeticException,但是如何?
      • 一旦 JIT 化,JVM 只是让 CPU 在硬件上进行划分,因此它的行为应该与其他原生应用程序完全一样,具有相同的 CPU 中断、操作系统交互和切换到进程.
      【解决方案6】:

      在 java 中有术语 exception 是短语 “异常事件”的简写。
      异常是发生在程序的执行,这会破坏程序指令的正常流程。
      当方法中发生错误时,该方法会创建一个对象并将其交给运行时系统。该对象称为异常对象,包含有关错误的信息,包括错误的类型和发生错误时程序的状态。创建异常对象并将其交给运行时系统称为抛出异常。
      如果您捕获了该异常,那么您可以在捕获异常后执行您想要执行的操作,否则默认处理程序会显示错误信息并终止程序...
      在 java 中除以零作为 ArithmeticException

      Exception in thread "main" java.lang.ArithmeticException: / by zero 
      

      【讨论】:

      • 一旦 JIT 化,JVM 只是让 CPU 在硬件上进行划分,因此它的行为应该与其他原生应用程序完全一样,具有相同的 CPU 中断、操作系统交互和切换到进程.所以 OP 中的问题是我们可以在哪里观察到操作系统的交互。
      猜你喜欢
      • 2011-02-11
      • 2018-11-22
      • 1970-01-01
      • 2017-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多