OutOfMemoryError异常
OutOfMemoryError异常(简称OOM)是Java虚拟机中一个比较常见的异常,它的情况有很多种,我们就以HotSpot的虚拟机为例,讲解一下常见的异常。
Java堆溢出
Java堆是在虚拟机中,所有线程共享的一个堆栈,我们如果在程序中不断地添加一个对象而不去销毁,只会让虚拟机的堆内存填满直到溢出,这是一种常见的错误。
1 | # 默认的jvm设置 |
这是IDEA中的默认的虚拟机配置,可以自行改动。
java堆前面说过,是所有线程共享的,java程序代码在某些时候出现的错误操作,会导致内存溢出,这个和C++类似,不过Java的虚拟机不仅仅支持了代码的跨平台性,还拥有着自动的内存回收功能,下面来看看Java虚拟机在操作不当时候会出现的错误吧。
注1:在idea使用虚拟机设置时,点击上方的run-edit configuration-vm option
注2:以下实验默认基于JDK8,否则会特别声明版本。
1 |
|
像这样不断增加对象的方式,会使得Java堆溢出。在idea中,可以直接点击test.java.14直接找到是哪里出现了错误。
虚拟机栈和本地方法溢出
过多的循环
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,虽然在Java虚拟机规范当中,是允许Java虚拟机选择是否支持栈动态扩展的,但是在HotSpot中没有这个选项。
1 | //使用-Xms128k |
这个表示着不断地加入循环,在不断地压栈过程中极大的增加了栈的深度,导致了内存溢出。对于不同的操作系统,栈容量最小值可能有所限制,比如Windows最小不能低于180k,而Linux最小不能低于228k。当然,也不是仅仅使用循环过深才导致的,也会有创建线程过多,导致内存溢出。
过多的线程
1 | //不在虚拟机上,千万不要尝试,有死机风险 |
在高并发环境下,产生过多线程在服务器中是比较正常的事情了。但像这种产生过多的线程,又不能减少线程数的情况下,去减少最大堆和减少栈容量是比较好的选择。
栈容量无法申请足够的内存
第一个实验都是基于虚拟机栈不能允许动态扩展的前提下,但如果允许动态扩展,却无法申请到足够内存时,也会抛出StackOverflowError异常。
1 |
|
实验表明,无论是栈帧太大,还是虚拟机栈容量大小,当新的栈帧内存无法分配时,都会抛出StackOverflowError异常。
方法区和运行时常量池溢出
常量池溢出
方法区曾被称之为永久代,但在jdk7以及之后开始逐步去永久代了,并且在JDK8当中,使用了元空间去代替。方法区存放的是常量池在不断存放过多的常量后也会溢出。以下实验先使用JDK6进行。
1 | //JDK6 |
这里的intern()是Native方法,作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象,否则将此String对象包含的字符串添加到常量池中,并返回此string对象的引用。如此一来,添加过多的常量,却不进行GC则导致的常量池溢出异常。
那么接下来是JDK7
1 | //JDK7 |
如果在JDK7中,仍然使用 VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M去进行的话,将会永久的运行下去,因为在JDK7中,已经把字符串常量池移动到了Java堆中。所以可以使用-Xmx6M去限制最大的堆,就可以看到不同的结果,结果表明是Java堆溢出。
JDK6和JDK7
还有一个比较重要的案例是:
1 | //JDK7 |
这个案例在jdk6中会出现两个false,但是在jdk7中会出现一个true和一个false,这是因为在jdk6中,intern()会返回首次遇到的字符串复制在永久代中,而StringBuilder则是在堆中,所以两次的引用都不一样。
而在jdk7中,intern()不会再去复制,而只是在常量池中记录首次出现的引用,因此str1返回的字符串,都是在堆上的,而str2的字符串,在常量池中已经有它的引用了,所以str2的intern()还是返回的常量池的引用。
方法区溢出
1 |
|
方法区内存溢出属于比较常见的溢出异常。一个类要被垃圾收集器回收,判定条件是比较苛刻的。像这样的情况,产生大量的class,就难以被回收,如此类似的还有很多JSP文件。
在JDK8以后,永久代便安全退出了历史舞台,元空间作为代替者登场。元空间是一个很大的改变,比如像前面的这一些测试,已经很难再使虚拟机产生方法区溢出异常了。我们看看元空间的一些防御措施:
—XX:MaxMetaspaceSize:设置元空间的最大值,默认为-1,即不受限制,或者说只受限于本地内存大小。
—XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:MaxMetaspaceFreeRatio。
本机内存直接溢出
1 |
|
在这一段代码中,通过了反射去获取Unsafe的实例,而unsafe.allocateMemory(_1MB);在不断向系统申请内存分配,却又不进行销毁,导致的本地内存溢出异常。