1.堆溢出
原因:大量对象占据了堆空间,而这些对象都有强引用导致无法回收,当对象大小之和>Xmx参数指定的堆大小时导致溢出!
List<byte[]> list = new ArrayList<>(); for (int i = 0; i < 10240; i++) { list.add(new byte[1024 * 1024]); }
解决方法是使用-Xmx增大堆大小,但是堆空间毕竟不能无限增长,所以需要使用MAT和VM等工具找到大量占用堆空间的对象做出合理的程序优化。
直接内存溢出
NIO直接向操作系统申请,但是由于没有内JVM托管,如果使用不当也会导致内存溢出。
过多线程导致OOM
因为每个线程需要占用系统内存,当线程过多可能导致OOM,线程的栈空间是在堆外分配,和直接内存很相似,如果要系统支持更多的线程,那么应该使用小的堆空间。
解决办法:
1).减少堆空间 -Xmx512m 这样操作系统可以预留更多内存用于线程创建 因此程序可以正常运行
2).减少线程所占的内存空间,使用-Xss可以指定栈空间 -Xmx1g -Xss128k
使用1G堆空间,但是栈空间减少到128K,剩余可以用的内存可以容纳更多的线程,但是减少栈空间,栈溢出的风险增加。
3).减少线程总数
2.栈溢出
java虚拟机规范定义了两种异常与栈空间有关:*Error和OutOfMemoryError
线程计算过程中 栈深度>最大可用栈深度 抛出*Error
如果栈可以动态扩展,如果扩展过程中没有足够内存空间支持会抛出OutOfMemoryError
-Xss设置栈大小,栈大小决定了函数调用的可达深度
虚拟机栈在运行时使用了栈帧的数据结构保持上下文数据,栈帧中存放了局部变量表,操作数栈,动态连接方法和返回地址等信息。
每一个方法的调用都伴随着栈帧的入栈操作,函数返回表示出栈。如果参数和局部变量过多那么栈帧的局部变量表会变大。
无限递归导致
public class TestDemo { private int count = 0; public void recursion() { count++; recursion(); } @Test public void testStack() { try { recursion(); } catch (Throwable e) { System.out.println("深度>>>" + count); e.printStackTrace(); } } }
jclasslib可以查看class文件每个方法分配的局部变量表内容,
下载地址:https://github.com/ingokegel/jclasslib/releases
在栈帧中与性能调优关系最密切的就是局部变量表:函数的参数+函数内局部变量,局部变量表以字为单位,一个字32位长,long和double占2字,其余1字。
对于非static方法虚拟机还将对象this作为参数通过局部变量表传递给当前方法。
3.永久区溢出
如果系统定义了太多的类型,那么永久区会溢出,Jdk1.8中永久区被称为元数据的区域代替,但是功能是类似的,都是保持类的元信息。
例如使用Cglib动态产生新类(而不是new对象)N次会都爱吃OOM异常。
解决办法:
1.增加MaxPermSize
2.减少系统需要的类的数量
3.使用ClassLoader合理的装载类定期回收
参考《Java程序性能优化 让你的Java程序更快、更稳定》