Java Hotspot JVM如何处理x86上的整数除数溢出?

Java Hotspot JVM如何处理x86上的整数除数溢出?,java,jvm,jvm-hotspot,Java,Jvm,Jvm Hotspot,在Java中划分两个ints没有什么特别之处。除非处理两种特殊情况中的一种: 除以零。(JVM要求虚拟机抛出算术异常) 除法溢出(Integer.MIN\u VALUE/-1,JVM要求结果等于Integer.MIN\u VALUE)(此问题专门针对这种情况) 发件人: 有一种特殊情况不满足此规则:如果被除数是int类型的最大可能大小的负整数,且除数是-1,则发生溢出,结果等于被除数。尽管存在溢出,但在这种情况下不会引发异常 在我的计算机上(x86_64)本机分区产生一个SIGFPE错误 当我编

在Java中划分两个
int
s没有什么特别之处。除非处理两种特殊情况中的一种:

  • 除以零。(JVM要求虚拟机抛出
    算术异常
  • 除法溢出(
    Integer.MIN\u VALUE/-1
    ,JVM要求结果等于
    Integer.MIN\u VALUE
    )(此问题专门针对这种情况)
  • 发件人:

    有一种特殊情况不满足此规则:如果被除数是
    int
    类型的最大可能大小的负整数,且除数是
    -1
    ,则发生溢出,结果等于被除数。尽管存在溢出,但在这种情况下不会引发异常

    在我的计算机上(
    x86_64
    )本机分区产生一个
    SIGFPE
    错误

    当我编译以下C代码时:

    #包括
    #包括
    整数除法(整数a,整数b){
    int r=a/b;
    printf(“%d/%d=%d\n”,a、b、a/b);
    返回r;
    }
    int main(){
    除法(整数最小值,-1);
    返回0;
    }
    
    我得到的结果(在x86上):

    tmp$gcc division.c
    tmp美元/年输出
    浮点异常(内核转储)
    
    在ARM上编译的完全相同的代码(
    aarch64
    )生成:

    -2147483648/-1=-2147483648
    
    因此,在x86上,热点虚拟机似乎需要做额外的工作来处理这种情况

    • 在这种情况下,虚拟机要做什么才能在编译代码中不损失太多性能
    • 它是否利用POSIX系统中信号处理的可能性?如果是,它在Windows上使用什么
    在这种情况下,虚拟机如何做才能不损失性能 编译代码太多

    他们什么也不做。它只是作为if语句实现的

    基于目标体系结构有不同的字节码解释器,但我看了一下,它们的实现都是相同的

    我不确定这句话指的是什么。我在JDK邮件列表中找不到任何关于这个方法的有趣的提及,如果我想解释一些历史性的决定,这是我通常的选择


    无论如何,强调“可以”这个词。无论他们的意思是什么,他们都不会这么做。

    你说得对-由于特殊情况,HotSpot JVM不能盲目使用
    idiv
    cpu指令

    因此,JVM执行一个额外的检查,
    Integer.MIN\u值是否除以
    -1
    。此检查同时存在于和中

    如果我们用
    -XX:+printsassembly
    检查实际编译的代码,我们将看到如下内容

      0x00007f212cc58410:   cmp    $0x80000000,%eax    ; dividend == Integer.MIN_VALUE?
      0x00007f212cc58415:   jne    0x00007f212cc5841f
      0x00007f212cc58417:   xor    %edx,%edx
      0x00007f212cc58419:   cmp    $0xffffffff,%r11d   ; divisor == -1?
      0x00007f212cc5841d:   je     0x00007f212cc58423
      0x00007f212cc5841f:   cltd   
      0x00007f212cc58420:   idiv   %r11d               ; normal case
      0x00007f212cc58423:   mov    %eax,0x70(%rbx)
    
    但是,正如您可能注意到的,没有检查除数==0。这被认为是一种例外情况,在正常程序中永远不会发生这种情况。这称为隐式异常。JVM记录这种异常可能发生的位置,并依赖操作系统信号(或Windows术语中的异常)来处理这种情况

    见:


    但是,如果隐式异常在同一位置发生得太频繁,JVM会对编译后的代码进行去优化,然后使用显式零检查重新编译(以避免频繁信号处理的性能损失)。

    您提到的代码永远不会在常规热点构建中执行。这段代码是“C++解释器”的一部分,这是一个用于各种测试和实验的玩具“零汇编”解释器。普通的JDK二进制文件甚至不包含此代码。实际执行的代码是
      0x00007f212cc58410:   cmp    $0x80000000,%eax    ; dividend == Integer.MIN_VALUE?
      0x00007f212cc58415:   jne    0x00007f212cc5841f
      0x00007f212cc58417:   xor    %edx,%edx
      0x00007f212cc58419:   cmp    $0xffffffff,%r11d   ; divisor == -1?
      0x00007f212cc5841d:   je     0x00007f212cc58423
      0x00007f212cc5841f:   cltd   
      0x00007f212cc58420:   idiv   %r11d               ; normal case
      0x00007f212cc58423:   mov    %eax,0x70(%rbx)
    
          if (sig == SIGFPE  &&
              (info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV)) {
            stub =
              SharedRuntime::
              continuation_for_implicit_exception(thread,
                                                  pc,
                                                  SharedRuntime::
                                                  IMPLICIT_DIVIDE_BY_ZERO);