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概念)比较有意思,这个可以仔细看下。