1、内存溢出
内存溢出:OOM(OutOfMemoryError)异常,即程序需要内存超出了虚拟机可以分配内存的最大范围。在Java 虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他区域都可能发生异常。
2、内存溢出区域
常见的内存溢出分为以下几种:
2.1 Java堆溢出
Java 堆用于存储对象实例,只要不断地创建对象,并且防止垃圾回收机制清除这些对象,那么在对象数量达到最大堆限制就会产生内存溢出异常。
测试方案:无限循环new对象实例出来,在List中保存引用,防止GC回收,最终会产生OOM ,异常堆栈信息并提示Java heap space。
2.2 虚拟机栈和本地方法栈溢出
关于虚拟机栈和本地方法栈,Java虚拟机规范中定义了两种异常:
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError 异常(同一个栈堆满了内存)。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。 (多个线程的栈不停的横向扩展空间)
测试方案: 单线程条件下,通过不断递归调用方法,如不断累加的方法,如下所示:
publicclass JavaVMStackSOF{
privateint stackLength=1;
publicvoidstackLeak(){
stackLength++;//累加变量
stackLeak();//调用自身
}
}
最终会产生StackOverflowError栈溢出异常;
多线程条件下,无限循环地创建线程,并为每个线程无限循环的增加内存,最终会导致OutOfMemoryError异常。
这里有一点要重点说明,在多线程情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。操作系统为每个进程分配的内存是有限制的,虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,忽略掉程序计数器消耗的内存(很小),以及进程本身消耗的内存,剩下的内存便给了虚拟机栈和本地方法栈,栈中每个线程分配到的栈容量越大,可以建立的线程数量自然就越少。因此,如果是建立过多的线程导致的内存溢出,在不能减少线程数的情况下,就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。
2.3 方法区和运行时常量池溢出
运行时常量池是方法区的一部分。方法区用于存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。测试方法:
(1)对于非常量池部分,运行时生成大量的动态类填满方法区;
(2)对于常量池部分,无限循环调用String的intern()方法产生不同的String对象实例,并在List中保存其引用,以防止被GC回收,最终会产生溢出。
2.4 本机直接内存溢出
此类内存溢出一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,可以考虑一下是不是这方面原因。
3、内存泄露
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你OOM。
Java内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收。
3.1 内存泄露场景:具体主要有如下几大类:
3.1.1 静态集合类引起内存泄漏
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++){
Object o = newObject();
v.add(o);
o = null