【问题标题】:How do you crash a JVM?如何使 JVM 崩溃?
【发布时间】:2010-09-09 01:48:48
【问题描述】:

我正在阅读一本关于编程技巧的书,其中作者问受访者:“你是如何使 JVM 崩溃的?”我认为您可以通过编写一个最终会耗尽所有内存的无限 for 循环来做到这一点。

有人知道吗?

【问题讨论】:

标签: java jvm


【解决方案1】:

我不会将抛出 OutOfMemoryError 或 StackOverflowError 称为崩溃。这些只是正常的例外。要真正使 VM 崩溃,有 3 种方法:

  1. 使用 JNI 并在本机代码中崩溃。
  2. 如果没有安装安全管理器,您可以使用反射使 VM 崩溃。这是特定于 VM 的,但通常 VM 会在私有字段中存储一堆指向本机资源的指针(例如,指向本机线程对象的指针存储在 java.lang.Thread 的长字段中)。只需通过反射更改它们,VM 迟早会崩溃。
  3. 所有虚拟机都有错误,因此您只需触发一个即可。

对于最后一种方法,我有一个简短的示例,它将很好地使 Sun Hotspot VM 崩溃:

public class Crash {
    public static void main(String[] args) {
        Object[] o = null;

        while (true) {
            o = new Object[] {o};
        }
    }
}

这会导致 GC 中的堆栈溢出,因此您不会收到 StackOverflowError 而是真正的崩溃,包括 hs_err* 文件。

【讨论】:

  • 哇!这会使 Sun Java 5、Sun Java 6 和 OpenJDK 6(在 Ubuntu 9.04 上)崩溃,没有 hs_err* 文件,但只有“分段错误!” ...
  • System.exit() 是一种更容易让 JVM 崩溃的方法(除非安装了安全管理器)
  • 不知道什么时候修复的,只是在 1.7.0_09 测试过,没问题。刚刚得到了预期:线程“main”中的异常java.lang.OutOfMemoryError:CrashJVM.main的Java堆空间(CrashJVM.java:7)
  • 我在 Intel Core i7 2.4 GHz/8 GB RAM/JDK1.7 64bit 上尝试了上面的代码,但 20 分钟后 JVM 仍然启动。 (有趣的是:我的笔记本电脑风扇比天空中的战斗机还响亮)。这个问题在 JDK 1.7+ 中修复了吗?
  • 在 JDK 1.8_u71 中,这会导致 java.lang.OutOfMemoryError: GC overhead limit exceeded
【解决方案2】:

JNI。事实上,对于 JNI,崩溃是默认的操作模式。你必须加倍努力才能让它不崩溃。

【讨论】:

【解决方案3】:

使用这个:

import sun.misc.Unsafe;

public class Crash {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    public static void crash() {
        unsafe.putAddress(0, 0);
    }
    public static void main(String[] args) {
        crash();
    }
}

这个类必须在引导类路径中,因为它使用的是受信任的代码,所以像这样运行:

java -Xbootclasspath/p:.崩溃

编辑:简体版,附有爱出风头的建议:

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
unsafe.putAddress(0, 0);

【讨论】:

  • 不使用 -Xbootclasspath 你也可以这样做:Field f = Unsafe.class.getDeclaredField( "theUnsafe" ); f.setAccessible( true ); unsafe = (Unsafe) f.get( null ); 对我来说很好。
  • Unsafe 根据定义,是“不安全的”。这有点作弊。
  • 确认在 Linux x64 上使用 JDK 8u131 中的 getDeclaredField 技巧工作,包括从 SIGSEGV 生成 hs_err_pid*.log
  • 使用Unsafe 不是作弊。 OP 不是在寻找编程问题的“干净”解决方案。他需要 jvm 以最丑陋的方式崩溃。做讨厌的本地事情是可以做到这一点的,这正是Unsafe 所做的。
  • 很高兴地报告,这仍然像 2021 年宣传的那样有效。我需要一种方法来生成 hs_err.pid 文件,以确保我的云基础设施正确复制它们。只需要一个四行函数!
【解决方案4】:

我来到这里是因为我在 The Passionate Programmer 中也遇到了这个问题,作者是 Chad Fowler。对于那些无法获得副本的人,该问题被设计为一种过滤/测试,用于面试需要“真正优秀的 Java 程序员”的职位的候选人。

具体来说,他问:

您将如何用纯 Java 编写一个会导致 Java 虚拟机崩溃的程序?

我用 Java 编程超过 15 年,我发现这个问题既令人费解又不公平。正如其他人所指出的,Java 作为一种托管语言,专门设计为不会崩溃。当然总有 JVM 的 bug,但是:

  1. 经过 15 年以上的生产级 JRE,这种情况很少见。
  2. 任何此类错误都可能在下一个版本中得到修补,那么作为程序员,您有多大可能遇到并回忆当前 JRE show-stoppers 集的详细信息?

正如其他人所提到的,一些通过 JNI 的本机代码是使 JRE 崩溃的可靠方法。但是作者特意提到纯Java,所以就不说了。

另一种选择是提供 JRE 虚假字节码;将一些垃圾二进制数据转储到 .class 文件并要求 JRE 运行它很容易:

$ echo 'crap crap crap' > crap.class
$ java crap
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1668440432 in class file crap

这算不算?我的意思是 JRE 本身并没有崩溃。它正确地检测到虚假代码,报告它并退出。

这给我们留下了最明显的解决方案,例如通过递归破坏堆栈,通过对象分配耗尽堆内存,或者简单地抛出RuntimeException。但这只会导致 JRE 以 StackOverflowError 或类似异常退出,这再次并不是真正的崩溃

那么还剩下什么?我真的很想听听作者真正想到的正确解决方案。

更新:Chad Fowler responded here

PS:这是一本非常棒的书。在学习 Ruby 时,我选择了它作为精神上的支持。

【讨论】:

  • 这样的面试问题可以作为 Java 开发人员的试金石,他们已经有足够长的时间来 1) 目睹了(许多)JVM 崩溃,并且 2) 已经得出了一些结论,或者更好的是,作为 (self-宣布)Java 专家已尝试了解崩溃的根本原因。 Chad Fowler 在他的书中还指出,自称 Java 程序员“甚至想不出错误的答案”,即没有尝试过思考何时会出现一整类潜在问题。底线:这个问题是“如何防止 JVM 崩溃?”的问题。这显然更好。
  • 我会寻找那些只回答(就像这里的大多数人一样)“溢出堆栈”、system.exit() 或其他“正常”关闭的人,因为他们并不真正了解 JVM 或“崩溃”一词。认识到(正如你所做的那样)这是非常不正常的,这是识别更高级程序员的一个很好的方法。我同意你的第一个陈述,我认为这是一个很好的、完全公平的问题,可以问或被问——没有具体答案的总是最好的。
  • 迟到了,但字节码方法听起来很合理:如果你能设法用勺子喂食 vm JITTER 代码,你肯定可以通过摆弄寄存器或类似的东西来让它崩溃
【解决方案5】:

这段代码会以令人讨厌的方式使 JVM 崩溃

import sun.dc.pr.PathDasher; 

public class Crash
{
     public static void main(String[] args)
     {    
        PathDasher dasher = new PathDasher(null) ;
     }
}

【讨论】:

  • 将 JDK 1.7 与此代码一起使用时出现编译错误:访问限制:由于所需库 C:\Program Files\Java\jdk1.7.0_51\ 的限制,无法访问类型 PathDasher jre\lib\rt.jar
  • 这会在 JDK 1.8 中引发 InternalError。 JVM 不再失败。
【解决方案6】:

上次我尝试过会这样做:

public class Recur {
    public static void main(String[] argv) {
        try {
            recur();
        }
        catch (Error e) {
            System.out.println(e.toString());
        }
        System.out.println("Ended normally");
    }
    static void recur() {
        Object[] o = null;
        try {
            while(true) {
                Object[] newO = new Object[1];
                newO[0] = o;
                o = newO;
            }
        }
        finally {
            recur();
        }
    }
}

生成的日志文件的第一部分:

#
# An unexpected error has been detected by Java Runtime Environment:
#
#  EXCEPTION_STACK_OVERFLOW (0xc00000fd) at pc=0x000000006dad5c3d, pid=6752, tid=1996
#
# Java VM: Java HotSpot(TM) 64-Bit Server VM (11.2-b01 mixed mode windows-amd64)
# Problematic frame:
# V  [jvm.dll+0x2e5c3d]
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x00000000014c6000):  VMThread [stack: 0x0000000049810000,0x0000000049910000] [id=1996]

siginfo: ExceptionCode=0xc00000fd, ExceptionInformation=0x0000000000000001 0x0000000049813fe8 

Registers:
EAX=0x000000006dc83090, EBX=0x000000003680f400, ECX=0x0000000005d40ce8, EDX=0x000000003680f400
ESP=0x0000000049813ff0, EBP=0x00000000013f2df0, ESI=0x00000000013f0e40, EDI=0x000000003680f400
EIP=0x000000006dad5c3d, EFLAGS=0x0000000000010206

【讨论】:

  • 我对否决票感到有点好笑,因为这是仅有的两个显示如何仅使用 Java 代码执行此操作的答案之一,并且包含完整的代码。
  • 我同意投反对票是没有根据的——这是一个真正的崩溃,但你会如何回答面试问题。除非你之前研究过并记住了这一点,否则你不能在面试中给出它作为答案,如果可以的话,我无论如何都会考虑一个中立到糟糕的答案——它并没有显示你将如何解决这个问题。我希望您所做的是 google 用于 jvm 漏洞利用并实施一个很好的答案。
  • @BillK - 不,以上完全是我自己的作品,虽然我是几年前想出的,尝试了各种东西。在一次采访中,我可能会说“我已经做到了,使用 try/catch 和递归,但我现在无法说出确切的代码。”
  • 详细信息是什么(JDK 版本,任何 VM 参数)?不确定“11.2-b0”是什么版本。我正在运行它,但它只消耗大量 CPU。
  • @Hot Licks:JVM 崩溃示例中的概念是什么?为什么 JVM 通过代码崩溃。所有线程都有单独的堆栈线程...
【解决方案7】:

完美的 JVM 实现永远不会崩溃。

要使 JVM 崩溃,除了 JNI,您还需要在 VM 本身中找到错误。无限循环只会消耗 CPU。在构建良好的 JVM 中,无限分配内存只会导致 OutOfMemoryError。这可能会导致其他线程出现问题,但一个好的 JVM 仍然不应该崩溃。

如果你能在VM的源代码中发现一个bug,例如在VM的实现中导致内存使用的分段错误,那么你实际上可以崩溃它。

【讨论】:

    【解决方案8】:

    如果您想使 JVM 崩溃 - 在 Sun JDK 1.6_23 或更低版本中使用以下命令:

    Double.parseDouble("2.2250738585072012e-308");
    

    这是由于 Sun JDK 中的 bug - 在 OpenJDK 中也可以找到。 从 Oracle JDK 1.6_24 开始,此问题已得到修复。

    【讨论】:

      【解决方案9】:

      取决于你所说的崩溃。

      您可以进行无限递归以使其耗尽堆栈空间,但这会“优雅地”崩溃。你会得到一个异常,但 JVM 本身会处理一切。

      您还可以使用 JNI 调用本机代码。如果你做得不好,那么你可以让它很难崩溃。调试这些崩溃是“有趣的”(相信我,我必须编写一个大的 C++ DLL,我们从签名的 java 小程序中调用它)。 :)

      【讨论】:

        【解决方案10】:

        最接近单个“答案”的是System.exit(),它会立即终止 JVM,而无需进行适当的清理。但除此之外,本机代码和资源耗尽是最有可能的答案。或者,您可以在 Sun 的错误跟踪器上查找您的 JVM 版本中的错误,其中一些允许可重复的崩溃场景。在 32 位版本(我们现在通常使用 64 位)下接近 4 Gb 内存限制时,我们曾经遇到过半定期崩溃。

        【讨论】:

        • 没有适当的清理?你确定吗?文档说“通过启动其关闭序列来终止当前正在运行的 Java 虚拟机......所有已注册的关闭挂钩(如果有)都已启动......所有未调用的终结器都运行” - 这不是正确的清理吗?
        • 这不是让 JVM 崩溃,而是有目的地明确地开始有序地关闭执行。
        • 更接近崩溃 jvm 的是 Runtime.getRuntime().halt(status)。根据文档,“如果启用了退出时的终结,则此方法不会导致启动关闭挂钩并且不会运行未调用的终结器”。仍然不是崩溃,但比 System.exit 更接近。
        • 这真是一个糟糕的答案。
        【解决方案11】:

        Jon Meyer 的书Java Virtual Machine 有一个导致 JVM 核心转储的一系列字节码指令的示例。我找不到这本书的副本。如果有人有,请查找并发布答案。

        【讨论】:

          【解决方案12】:

          在带有 wmp10 jre6.0_7 的 winxpsp2 上

          Desktop.open(uriToAviOrMpgFile)

          这会导致生成的线程抛出未捕获的 Throwable 并崩溃热点

          YMMV

          【讨论】:

            【解决方案13】:

            损坏的硬件可能导致任何程序崩溃。我曾经有一个应用程序崩溃在特定机器上可重现,而在具有完全相同设置的其他机器上运行良好。原来那台机器的内存有问题。

            【讨论】:

            • 如何确保您的进程每次运行时都位于同一个页面地址?这个答案就像在说“如果海啸袭击了你的村庄,你的代码肯定会崩溃”
            • @clockw0rk: 如果故障内存靠近地址空间的开头,并且您的程序使用了大量内存,那么您很有可能会碰到它。特别是因为它通常不仅仅是一个不能正常工作的字节。
            【解决方案14】:

            最短路径:)

            public class Crash
            {
                public static void main(String[] args)
                {
                    main(args);
                }
            }
            

            【讨论】:

            • 不会崩溃。它给出了编译时错误Exception in thread "main" java.lang.StackOverflowError at Test.main。我正在使用 jdk1.8.0_65
            • @QuaziIrfan 这是运行时错误,不是编译时错误!
            【解决方案15】:

            不是崩溃,但比使用System.exit 的公认答案更接近崩溃

            你可以通过调用来停止 JVM

            Runtime.getRuntime().halt( status )

            根据文档:-

            “如果启用了 finalization-on-exit,此方法不会启动关闭挂钩,并且不会运行未调用的终结器”。

            【讨论】:

              【解决方案16】:

              这里详细解释了导致 JVM 核心转储(即崩溃)的原因: http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_17534

              【讨论】:

                【解决方案17】:

                如果你想假装你的内存用完了,你可以这样做

                public static void main(String[] args) {
                    throw new OutOfmemoryError();
                }
                

                我知道通过调用本机方法(内置方法)导致 JVM 转储错误文件的几种方法,但最好您不知道如何执行此操作。 ;)

                【讨论】:

                  【解决方案18】:

                  如果您将崩溃定义为由于未处理的情况(即没有 Java 异常或错误)而导致进程中止,那么这不能在 Java 中完成(除非您有权使用 sun.misc.Unsafe 类) .这就是托管代码的全部意义所在。

                  本机代码中的典型崩溃是通过取消引用指向错误内存区域(空地址或未对齐)的指针而发生的。另一个来源可能是非法机器指令(操作码)或来自库或内核调用的未处理信号。如果 JVM 或系统库有 bug,都可以触发。

                  例如,JIT(生成)代码、本机方法或系统调用(图形驱动程序)可能会出现导致真正崩溃的问题(当您使用 ZIP 函数并且它们耗尽内存时发生崩溃是很常见的)。在这些情况下,JVM 的崩溃处理程序会启动并转储状态。它还可以生成操作系统核心文件(Windows 上的 Watson 博士和 *nix 上的核心转储)。

                  在 Linux/Unix 上,您可以通过向正在运行的进程发送信号来轻松地使 JVM 崩溃。注意:您不应为此使用SIGSEGV,因为在大多数情况下,Hotspot 会捕获此信号并将其作为 NullPointerException 重新抛出。所以最好发送SIGBUS 之类的。

                  【讨论】:

                    【解决方案19】:

                    JNI 是一个很大的崩溃源。您也可以使用 JVMTI 接口崩溃,因为它也需要用 C/C++ 编写。

                    【讨论】:

                      【解决方案20】:

                      如果您创建的线程进程无限生成更多线程(这会生成更多线程,这...),您最终会在 JVM 本身中导致堆栈溢出错误。

                      public class Crash {
                          public static void main(String[] args) {
                      
                              Runnable[] arr = new Runnable[1];
                              arr[0] = () -> {
                      
                                  while (true) {
                                      new Thread(arr[0]).start();
                                  }
                              };
                      
                              arr[0].run();
                          }
                      }
                      

                      这给了我输出(5 分钟后,注意你的 ram)

                      An unrecoverable stack overflow has occurred.
                      #
                      # A fatal error has been detected by the Java Runtime Environment:
                      #
                      #  EXCEPTION_STACK_OVERFLOW (0xc00000fd) at pc=0x0000000070e53ed7, pid=12840, tid=0x0000000000101078
                      #
                      # JRE version: Java(TM) SE Runtime Environment (8.0_144-b01) (build 1.8.0_144-b01)
                      # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode windows-amd64 compressed oops)
                      # Problematic frame:
                      # 
                      

                      【讨论】:

                      • 如果您使用进程执行此操作,您有时会导致计算机崩溃。您可以通过使用运行时 exec(“调用您的 JAR 的 .bat 文件”)来完成 -> jar 在其构造函数中打开 2 个新的运行时 execs() -> 整洁的分叉炸弹,放入一个摇摆元素,您就有了经典的 90 年代黑客感觉 lulz
                      【解决方案21】:

                      如果“崩溃”是指 JVM 的突然中止,会导致 JVM 写入其 hs_err_pid%p.log,您可以这样做。 p>

                      将 -Xmx 参数设置为一个很小的值并告诉 JVM 在内存不足时强制崩溃:

                       -Xmx10m -XX:+CrashOnOutOfMemoryError
                      

                      明确地说,如果没有上面的第二个参数,它只会导致 jvm 终止并出现 OutOfMemoryError,但它不会“崩溃”或突然中止 jvm。

                      当我尝试测试 JVM -XX:ErrorFile arg 时,这种技术被证明是有用的,它控制应该在哪里写入这样的 hs_err_pid 日志。我在这里找到了这篇文章,同时试图找到迫使这种崩溃的方法。当我后来发现上述方法最适合我的需要时,我想将其添加到此处的列表中。

                      最后,FWIW,如果有人在您的 args 中设置了 -Xms 值(比上面的值更大)时可以对此进行测试,您也需要删除或更改它,否则您将不会得到崩溃,但只是 jvm 启动失败,报告“初始堆大小设置为大于最大堆大小的值”。 (如果将 JVM 作为服务运行,例如与某些应用服务器一起运行,这不会很明显。再次,它咬我,所以我想分享它。)

                      【讨论】:

                      • 它适用于测试 JVM -XX:ErrorFile arg @Charlie
                      • 我不知道:桑迪,您的评论是否只是在确认我所说的内容,“当我尝试测试 JVM -XX:ErrorFile arg 时,这种技术证明很有帮助”?如果是这样,谢谢。如果您认为我在质疑它是否会“有效”地进行测试,那我不是。 :-)
                      【解决方案22】:

                      最短?使用 Robot 类触发 CTRL+BREAK。当我试图在不关闭控制台的情况下关闭程序时发现了这一点(它没有“退出”功能)。

                      【讨论】:

                      • 老问题 - 希望将来有人能从您的回答中受益。
                      • 我喜欢这个
                      【解决方案23】:

                      这算不算?

                      long pid = ProcessHandle.current().pid();
                      try { Runtime.getRuntime().exec("kill -9 "+pid); } catch (Exception e) {}
                      

                      它仅适用于 Linux 和 Java 9。

                      出于某种原因,我不明白,ProcessHandle.current().destroyForcibly(); 不会杀死 JVM 并抛出 java.lang.IllegalStateException 并显示消息 destroy of current process not allowed

                      【讨论】:

                        【解决方案24】:

                        如果将无限 for 循环更改为对同一函数的递归调用,则会出现堆栈溢出异常:

                        public static void main(String[] args) {
                            causeStackOverflow();
                        }
                        
                        public void causeStackOverflow() {
                            causeStackOverflow();
                        }
                        

                        【讨论】:

                          【解决方案25】:

                          我现在正在这样做,但不完全确定如何... :-) JVM(和我的应用程序)有时会完全消失。没有抛出错误,没有任何记录。在没有任何警告的情况下立即从工作变为完全不运行。

                          【讨论】:

                          • 开始检查你的硬件,尤其是你的内存!
                          • 不幸的是,它在多台机器上运行得很好。只有这个特定的应用程序可以做到这一点(而且它不是内存或处理器密集型的)。
                          【解决方案26】:

                          在尝试复制 JVM 崩溃时遇到此问题。

                          Jni 有效,但需要针对不同平台进行调整。 最终,我使用这种组合使 JVM 崩溃

                          1. 使用此 JVM 选项启动应用程序-XX:+CrashOnOutOfMemoryError
                          2. 使用long[] l = new long[Integer.MAX_VALUE]; 触发OOM

                          然后JVM会崩溃并生成崩溃日志。

                          【讨论】:

                          • 不正确,因为这会导致 OOM 而不会崩溃。
                          【解决方案27】:

                          如果“崩溃”是任何中断 jvm/程序正常终止的东西,那么未处理的异常可能会执行此操作。

                          public static void main(String args[]){
                             int i = 1/0;
                             System.out.print(i); // This part will not be executed due to above  unhandled exception
                            }
                          

                          所以,这取决于什么类型的 CRASH ?!

                          【讨论】:

                          • 抛出异常并不是崩溃。
                          • 未处理的运行时异常是崩溃,但ArithmeticException
                          猜你喜欢
                          • 1970-01-01
                          • 2014-10-13
                          • 2016-10-11
                          • 2012-01-14
                          • 1970-01-01
                          • 2013-10-22
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多