How to Crash Java VM
最近线上Java应用爆出了个很诡异的问题,需要理解Java虚拟机方面的知识,也正在补充JVM方面的知识;突然有个想法,如何人为的让JVM爆掉(Crash)呢?
这个想法说起来简单,实际上想考虑完全还是比较困难的。我将我自己想到的内容先放上来,后面会补充一些其它的知识。
1:利用JVM里面的一些非常规错误(Error),如StackOverflowError、OutOfMemoryError等。这个算是让虚拟机Crash的比较弱的条件,并不能算是JVM底层的问题,但是我们让JVM成功down掉了。
我们先看下StackOverflowError的情况:出现这种情况是因为虚拟机栈对于迭代深度有限制,当分配不足时,就会出现这个错误。
public class StackOverFlowCrash {
/**
* @param args
*/
public static void main ( String[] args ) {
// TODO Auto-generated method stub
main(args);
}
}
出现异常:Exception in thread "main" java.lang.StackOverflowError
该问题出现的原因是当调用函数时,会将这个函数信息放到这个线程的栈中,只要这个方法没有返回,这个栈就一直存在。如果方法的嵌套层次调用太多,导致超过栈设置大小,就产生StackOverflowError溢出异常。
实际上在使用栈空间不足时,都会产生这个StackOverflowError问题,如启用新线程时,如果栈空间不足就会出问题;或者在Native Method中申请超大内存时,也会产生这个Error问题。
public class OOMCrash {
/**
* @param args
*/
public static void main ( String[] args ) {
// TODO Auto-generated method stub
Object[] obj = new Object[Integer.MAX_VALUE];
}
public static void stackOverflow(){
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
这个比较常见,就是申请的内存超过JVM的限制,就会导致OutOfMemoryError问题。
方法区内存溢出
方法区是用于存放Java的类相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等;在类加载器完成class文件加载的时候,会将这些信息放到方法区。如果方法区的内存占用达到最大值(-XX:MaxPermSize),就会抛出OOM异常,导致虚拟机Crash掉。
这种情况的测试思路比较简单,就是在运行区产生大量的类去填充方法区,直到方法区溢出为止。可以借助CGLib实现,动态生成类。
2:JNI方法
如果调用JNI方法时出现异常,或者JNI代码中Crash掉的话,JVM也会相应的Crash掉。
3:Security相关的Crash,使用反射可以调用JVM的本地方法资源,这样也可以导致JVM Crash掉。
import sun.misc.Unsafe;
public class UnsafeCrash {
/**
* @param args
*/
private static final Unsafe unsafe = Unsafe.getUnsafe();
public static void crash() {
unsafe.putAddress(0, 0);
}
public static void main(String[] args) {
crash();
}
}
执行后可以看下下面的异常,是SecurityException,同样会让JVM不能正常工作。
java.lang.ExceptionInInitializerError
Caused by: java.lang.SecurityException: Unsafe
3:JVM(Native Code) bug
JVM本身也是个应用,其代码也有bug,如果能找到jvm本身的bug,应该就能使得JVM Crash掉。
这里有个很经典的实例,利用了JVM本身的bug。
public class Crash {
/**
* @param args
*/
public static void main ( String[] args ) {
Object[] o = null;
while (true) {
o = new Object[] {o};
}
}
}
执行后会产生一个JVM Crash掉的信息:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_STACK_OVERFLOW (0xc00000fd) at pc=0x000000006dc3f414, pid=15928, tid=15708
#
# JRE version: 6.0_20-b02
# Java VM: Java HotSpot(TM) 64-Bit Server VM (16.3-b01 mixed mode windows-amd64 )
# Problematic frame:
# V [jvm.dll+0x3af414]
#
# An error report file with more information is saved as:
# D:\workspace\jvmcrash\hs_err_pid15928.log
该bug问题比较严重,出现EXCEPTION_STACK_OVERFLOW提示,同时在应用目录下产生hs_err_pidXYZ.log。比较根本的原因是GC过程的栈信息出现Overflow问题,导致JVM Crash掉。该问题在1.6版本中存在,在1.7中出现了OOMError,并不会导致虚拟机本身出现问题,出现异常提示的原因也有不一样。
JVM的bug还可以看下这个实例:
import sun.dc.pr.PathDasher;
public class PathDasherCrash {
/**
* @param args
*/
public static void main ( String[] args ) {
PathDasher dasher = new PathDasher(null);
}
}
该方法执行后也会产生异常:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006dab3975, pid=16500, tid=15796
#
# JRE version: 6.0_20-b02
# Java VM: Java HotSpot(TM) 64-Bit Server VM (16.3-b01 mixed mode windows-amd64 )
# Problematic frame:
# V [jvm.dll+0x223975]
#
# An error report file with more information is saved as:
# D:\workspace\jvmcrash\hs_err_pid16500.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
注意是EXCEPTION_ACCESS_VIOLATION异常,这个异常的产生有多个方面的原因。
1:调用非安全方法
2:JVM对于传入参数的处理有问题
3:该类中的方法大部分是native方法,JVM调用的native方法中对于null参数的处理不正确。
根据以上的思路,JNI、Security Method、JVM bugs等方面都可以导致JVM不能正常运行。
不过导致的方法不一样,在Java中还有一些其它的表现如ByteBuffer的Direct内存分配超过限制、编译器bug(http://seanhe.iteye.com/blog/905997)等。
这个也算是从反向思路来学习Java Virtual Machine吧。在http://stackoverflow.com上有个关于这个话题的讨论,大家可以系统学习下,不过内容也不离本文;大家对于这个问题本身的讨论(JVM的Crash概念)比较有意思,这个可以仔细看下。