1.内存溢出
1.1 什么是Java的内存溢出?
在Java程序运行的过程中,经常会碰到以下错误:java.lang.OutOfMemoryError。
通俗讲,内存溢出是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError。
1.2 产生原因?
简单来讲为以下两点:
1. JVM内存过小
2. 产生过多的,没有被回收的垃圾
以下讨论主要基于JVM上不同内存区域的讨论
1.3 Java堆溢出
Java堆是存放对象实例的地方,当我们不断申请创建对象,并且保证这些对象始终可以从GC Roots可达,总容量就会触及最大堆的容量限制而抛出内存溢出异常
例如以下代码,将虚拟机的初始大小设为 20M ,并且不可变(将堆的最小值 -Xms 和 堆的最大值 -Xmx 设置为一样可以避免Java堆自动扩展!)
public class OOM {
/*
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
static class OOMObject{
}
public static void main(String[] args){
List list = new ArrayList<>();
while(true){
list.add(new OOMObject());
}
}
}
解决方法
常规处理方法是通过内存映像分析工具对 Dump 出来的堆转储快照进行分析。
1.首先分析是内存泄漏还是内存溢出
2.如果是内存泄漏通过工具查看泄露对象到 GC Roots 的引用链,分析垃圾收集器无法回收他们的原因,进而定位到出现问题的代码
3.如果不是内存泄漏,即对象都应该必须活着,就应该对比 JVM堆内存 和机器内存相比是否还有向上调整的空间;或者从代码上检查某些对象是否生命周期过长,持有状态时间过长,存储结构设计不合理等,尽量减少程序运行期间的内存消耗
1.4 虚拟机栈和本地方法栈溢出
1. 如果线程请求栈的深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常
2. 如果虚拟机的栈内存允许动态的扩展,当扩展栈的容量无法申请到足够的内存的时候,将抛出’OutOfMemoryError’异常
解决方法
1.出现 StackOverflowError 异常时,会有明确错误堆栈可供分析,比较容易定位问题所在,例如递归没有终止条件。栈深度大多数情况下到达1000-2000是没有问题的,对于正常方法的调用,这个深度完全是够用的。
2.但如果是因为建立过多线程导致内存溢出,在不能减少线程的数量的情况下,只能通过减少最大堆的容量或者减少栈的容量来获取更多的线程!
1.5 方法区和运行时常量池溢出
由于在JDK 8 以后,永久代退出了历史舞台,元空间作为其替代者登场,即元空间使用的是直接内存,受限于本机物理内存的大小,不再容易抛出方法区的内存溢出。而在JDK 8 之前,方法区的实现永久代是会因为加载了大量的类(比如CG Lib字节码技术)而抛出方法区内存溢出的,或者因为运行时常量池(JDK 6 还是属于方法区的一部分,抛出的是方法区的内存溢出;而JDK7之后便移到Java堆上,抛出的是java的堆内存溢出)而抛出对应区域的内存溢出!
1.6 本机直接内存溢出
直接内存可以通过 -XX:MaxDirectMemorySize 参数来指定,若不指定则默认与堆的最大值保持一致。
由直接内存导致的内存溢出,一个明显的特征就是在dump下的文件不会看到有明显的异常情况,或者该文件很小,而程序又直接或者间接的使用了 Direct Memory ,就应该去考虑是否是本机直接内存溢出
2. 内存泄漏
Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:
首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;
其次,这些对象是无用的,即程序以后不会再使用这些对象。
如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
3.两者联系
内存泄露会最终会导致内存溢出。
相同点:都会导致应用程序运行出现问题,性能下降或挂起。
不同点:
1. 内存泄露是导致内存溢出的原因之一,内存泄露积累起来将导致内存溢出。
1. 内存泄露可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。
本作品采用《CC 协议》,转载必须注明作者和本文链接