一 前言
OutOfMemoryError异常是在编程过程中因为程序的处理问题或者jvm参数配置的问题而导致的错误。在虚拟机的这几个运行时区域都有发生OutOfMemoryError的可能:java堆,虚拟机栈,本地方法栈 ,方法区,运行时常量池,直接内存。下面针对这些例举几个代码例子来说明。
二 java堆溢出
Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达的路径来避免垃圾回收机制来清除这些对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。
下面代码模拟:
import java.util.ArrayList;
import java.util.List;
public class HeapOom {
public static void main(String[] args) {
List<OomObject> list=new ArrayList<>();
while (true) {
list.add(new OomObject());
}
}
}
class OomObject{
}
然后设置其内存大小,可以缩小其内存大小,比如我设置最大内存为10m
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/dump/a.dump
这句话是在如果堆内存溢出会在d盘的dump文件下生成dump文件。
运行后会报错:
如何解决此问题:
可以通过内存分析工具对生成的dump文件进行,JDK提供了一些内存泄漏的分析工具,如jconsole,jvisualvm等,这里使用MAT(Memory Analyzer Tool)工具。
MAT如何具体使用可以参考这篇博客:Java内存分析工具MAT(Memory Analyzer Tool)安装使用实例
打开dominator_tree通过列名Shallow Heap 和 Retained Heap,Percentage获取相信的信息。找到percentage最大也就是占用内存最高的className,想了解Shallow Heap和 Retained Heap,可以参考此篇博客 Shallow Heap 和 Retained Heap的区别
我们找到了占用内存最大的class,点击拓展会发现OomObject对象占用了绝大多数内存
点击右键 选择exclude all phantom/weak/soft etc.references,排除虚引用/弱引用/软引用等的引用链,因为被虚引用/弱引用/软引用的对象可以直接被GC给回收,我们要看的就是某个对象否还存在Strong 引用链(在导出HeapDump之前要手动出发GC来保证),如果有,则说明存在内存泄漏
内存泄漏我们应该在使用的时候就应该避免,
1.好的办法是使用临时变量的时候,让引用变量在推出活动域后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄漏。
2.使用大对象时,在用完后赋值为null
3.程序进行字符串处理时,尽量避免使用String,而应该使用StringBuffer。
因为String类是不可变的,每一个String对象都会独立占用内存一块区域。
4.避免一些死循环等重复创建或对集合添加元素,撑爆内存
5.简洁数据结构、少用静态集合等
6.及时的关闭打开的文件,socket句柄等
7.多关注事件监听(listeners)和回调(callbacks),比如注册了一个listener,当它不再被使用的时候,忘了注销该listener,可能就会产生内存泄露
三 虚拟机栈与本地方法栈溢出
Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
StackOverFlowError: 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,(当前大部分的Java虚拟机都是可以动态扩展的,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemorryError异常。
1.单线程 环境下
使用-Xss参数减少栈内存容量,或者定义大量的本地变量,增大此方法的本地变量的长度,如下
public class StackSop {
private int stackLength=1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
StackSop oom=new StackSop();
try {
oom.stackLeak();
}finally {
System.out.println("stackLength:"+oom.stackLength);
}
}
}
2.如果测试不限于单线程,通过不断的建立线程的方式来产生内存溢出异常,但是这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系。原因:惭怍系统分配给每个进程的内存是有限制的。32位windows限制为2GB减去Xmx(最大堆容量)再减去MaxPermSize(最大方法区容量),程序计数器消耗内存可以忽略不计。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了,每个线程分配的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽
如果是建立过多的多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。