一:Java里内存溢出分为栈内存溢出和堆内存溢出,不过一般而言我们说某个Java应用发生了内存溢出就是指堆内存溢出,即OOM,对应java.lang.OutOfMemoryError这个异常(错误);而栈内存溢出则是java.lang.StackOverflowError异常;
二:Java里每个线程都有一个栈空间(栈内存),可以通过JVM参数-Xss256k来配置线程栈空间大小;而线程栈空间里还有栈帧的概念,线程上每调用一个方法都会压入一个栈帧(栈帧里存储该方法里的栈变量),比如主线程执行main方法,那么main方法就是主线程的一个栈帧,在main方法里再调用test()方法,则主线程又会压入新的栈帧来对应test()方法;当执行完毕方法返回后,比如test()方法执行完毕,那么线程会将对应的栈帧弹出,即释放由该方法创建的变量占用的栈内存;
三:正常情况下是不太可能出现栈内存溢出的,因为栈帧的存在,以及一个方法基本上都不会成千上万行,再加上存储在栈上的数据基本上都是变量类型,如int,double,引用之类的(就占几个字节),所以出现了栈内存溢出基本上都和递归函数有关;
在JVM里没有相关的配置参数来设置栈内存溢出后的操作,而且也是不需要的,因为StackOverflowError异常栈里就已经能够知道是哪些函数/方法导致了栈内存溢出(每个线程都有一个栈空间);
而堆内存溢出则不能够通过OutOfMemoryError异常栈里来判断是哪些方法导致的,举个例子-Xmx2g的应用,然后A线程里的一些方法通过创建变量使用了1g,B线程里的一些方法占用了0.95g,然后此时C线程执行方法里创建了部分对象导致超过了2g
产生OOM,那么异常显然是在C线程方法里抛出来的,所以异常栈信息也都是C线程里的一些方法,但是显然我们不能断言说主要是C线程里的方法导致了OOM;
四:堆内存溢出的情况是创建了大量的类,而这些类都还在使用,使得gc回收时无法回收,最终持续的创建对象就造成了堆内存溢出;
我们可以通过配置JVM参数来实现当出现OutOfMemoryError错误的时候,dump对应的堆内存使用数据,然后通过jvisualvm.exe这个jdk工具来分析产生OOM时的各个类的创建数量,占用内存,所属线程等信息来分析是哪里导致的OOM;
配置参数为:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apps/xx-service;第一个表示产生了OOM要dump出一个.hprof文件,第二个是dump的该文件存放在哪个目录;
还可以通过配置-XX:OnOutOfMemoryError="/opt/apps/xx-service/xx.sh"来实现当产生了OOM时,在抛出此OOM之前执行后面的命令/脚本,然后抛出OOM;
还可以通过配置-XX:+ExitOnOutOfMemoryError来实现当出现了OOM会自动关闭程序,注意如果和上面的一起配置,这个优先级是最低的,即会dump文件,也会执行命令后才关闭应用;