如何使 JVM 崩溃?

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

有人知道吗?

74833 次浏览

JNI .实际上,对于 JNI,崩溃是默认的操作模式。你得加倍努力才不会撞车。

那要看你说的坠机是什么意思了。

您可以执行无限递归以使其耗尽堆栈空间,但这将“优雅地”崩溃。您将得到一个异常,但是 JVM 本身将处理所有事情。

您还可以使用 JNI 调用本机代码。如果你做得不对,那么你可以让它坠毁得很厉害。调试这些崩溃是“有趣的”(相信我,我不得不编写一个大的 C + + DLL,我们从一个签名的 java applet 调用)。:)

如果您将那个无限 for 循环更改为对同一函数的递归调用,那么您将得到一个堆栈溢出异常:

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


public void causeStackOverflow() {
causeStackOverflow();
}

最接近单个“答案”的是 System.exit(),它在没有进行适当清理的情况下立即终止 JVM。但除此之外,本机代码和资源枯竭是最有可能的答案。或者,您可以在 Sun 的 bug 跟踪器上查找您的 JVM 版本中的 bug,其中一些允许可重复的崩溃场景。在32位版本(我们现在通常使用64位)下,当接近4Gb 内存限制时,我们会出现半规则的崩溃。

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

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

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

Jon Meyer 的书《 Java 虚拟机》中有一系列字节码指令的例子,这些指令导致了 JVM 的内核转储。我找不到这本书了。如果有人有一个,请查找并张贴答案。

我不会把抛出 OutOfMemory 错误或 StackOverflow 错误称为崩溃。这些只是普通的例外。要真正使虚拟机崩溃,有三种方法:

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

对于最后一个方法,我有一个简短的例子,它将使 Sun Hotspot VM 安静地崩溃:

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


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

这将导致 GC 中出现堆栈溢出,因此您将不会得到 StackOverflow 错误,而是一个真正的崩溃,包括 hs _ err * 文件。

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

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

例如,JITed (生成)代码、本机方法或系统调用(图形驱动程序)可能会导致真正的崩溃(当使用 ZIP 函数时,很容易出现崩溃,因为它们耗尽了内存)。在这些情况下,JVM 的崩溃处理程序启动并转储状态。它还可以生成一个 OS 核心文件(Windows 上的 Watson 博士和 * nix 上的 core dump)。

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

在 winxpsp2 w/wmp10 jre6.0 _ 7上

Open (uriToAviOrMpgFile)

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

YMMV

这里有一个关于导致 JVM 内核转储(即崩溃)的详细解释: Http://kb.adobe.com/selfservice/viewcontent.do?externalid=tn_17534

我现在正在做,但不完全确定如何... : :) JVM (和我的应用程序)有时会完全消失。没有抛出错误,没有任何记录。在没有任何警告的情况下,马上从工作变成不跑步。

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

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

我知道有几种方法可以通过调用本机方法(内置的方法)导致 JVM 转储错误文件,但是您最好不知道如何做到这一点。;)

JNI 是崩溃的主要来源。您还可以使用 JVMTI 接口崩溃,因为它也需要用 C/C + + 编写。

坏掉的硬件会使任何程序崩溃。我曾经有一个应用程序崩溃在一个特定的机器上可重现,而运行良好的其他机器与完全相同的设置。结果发现那台机器内存有问题。

用这个:

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-Xbootclassspath/p: . Crash

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

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

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

import sun.dc.pr.PathDasher;


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

如果你想让 JVM 崩溃——在 Sun JDK 1.6 _ 23或更低版本中使用以下代码:

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

这是由于 Sun JDK 的一个 臭虫——也可以在 OpenJDK 中找到。 这是从 Oracle JDK 1.6 _ 24开始修复的。

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

他特别问道:

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

我已经用 Java 编程超过15年了,我发现这个问题既令人困惑又不公平。正如其他人指出的那样,Java 作为一种托管语言,是专门设计的 不要撞车。当然总是有 JVM 错误,但是:

  1. 经过15年以上的生产级 JRE,这种情况很少见。
  2. 任何这样的错误都有可能在下一个版本中得到修补,那么作为一个程序员,您有多大可能会遇到并回想起当前一组 JRE show-stop 的细节呢?

正如其他人已经提到的,通过 JNI 的一些本机代码肯定会导致 JRE 崩溃。但是作者特别提到了 用纯爪哇语,所以这是不可能的。

另一种选择是向 JRE 提供伪字节码; 将一些垃圾二进制数据转储到。类文件,并要求 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 回应了这里

附言: 这是一本很棒的书,我在学习 Ruby 的时候选择了它作为精神支柱。

最快捷的方式:)

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

上次我尝试这样做的时候:

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

如果“崩溃”是从正常终止中断 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
}

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

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

可以通过调用

Runtime.getRuntime().halt( status )

根据文件:-

“此方法不会导致启动关闭挂钩,如果启用了终止即退出(finalization-on-exit) ,则不会运行未调用的终结器”。

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

如果您创建的线程进程无限地产生更多的线程(这会产生更多的线程,这... ...) ,您最终将在 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分钟后,看你的内存)

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:
#

这算吗?

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

它只适用于 Linux 和 Java9。

由于某种原因,我不明白,ProcessHandle.current().destroyForcibly();没有杀死 JVM,而是将 java.lang.IllegalStateException与消息 不允许销毁当前工艺一起抛出。

如果您所说的“崩溃”是指 JVM 比如会导致 JVM 写出它的 hs _ err _ pid% p.log的突然中止,那么您可以这样做。

将 -Xmx arg 设置为一个很小的值,并告诉 JVM 强制在 outofmemory 上崩溃:

 -Xmx10m -XX:+CrashOnOutOfMemoryError

需要说明的是,如果没有上面的第二个参数,它只会导致 jvm 终结出现 OutOfMemory 错误,但它不会“崩溃”或突然中止 jvm。

当我试图测试 JVM-XX: ErrorFile 参数时,这种技术被证明是有用的,该参数控制这样的 hs _ err _ pid 日志应该写入的位置。我在这里发现了这个帖子,同时试图找到迫使这种崩溃的方法。当我后来发现以上工作作为最容易为我的需要,我想添加到这里的清单。

最后,FWIW,如果有人可以在你的 args 中已经设置了一个-Xms 值(比上面的值更大)时测试它,你也可以移除或更改它,或者你不会得到一个崩溃,而只是 jvm 启动失败,报告“初始堆大小设置为比最大堆大小更大的值”。(如果将 JVM 作为服务运行,比如使用某些应用服务器,那么这一点就不明显了。再一次,它咬了我,所以我想分享它。)

在试图复制 JVM 崩溃时遇到这个问题。

Jni 可以工作,但是需要针对不同的平台进行调整。 最后,我使用这种组合使 JVM 崩溃

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

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