内存溢出的场景及解决办法

开发过程当中是否遇到 StackVoerflowError、Permgen space等错误?

怎么解决?java

 

对于java程序员来讲,在虚拟机自动内存管理机制的帮助下,不须要本身实现释放内存,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存这一切看起来很是美好,可是一旦出现内存溢出或者内存泄漏的问题,对于不熟悉jvm虚拟机是怎么使用内存的话,那么排查错误将会是一项很是艰巨的任务。因此在了解内存溢出以前先要搞明白JVM的内存模型。JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部份内存叫直接内存,属于操做系统的本地内存,也是能够直接操做的。程序员

  

内存溢出的场景面试

JVM运行时首先须要类加载器(classLoader)加载所需类的字节码文件。加载完毕交由执行引擎执行,在执行过程当中须要一段空间来存储数据(类比CPU与主存)。这段内存空间的分配和释放过程正是咱们须要关心的运行时数据区。内存溢出的状况就是从类加载器加载的时候开始出现的,内存溢出分为两大类:OutOfMemoryError和StackOverflowError。如下举出10个内存溢出的状况,并经过实例代码的方式讲解了是如何出现内存溢出的。shell

 

一.java堆内存溢出数据库

当出现java.lang.OutOfMemoryError:Java heap space异常时,就是堆内存溢出了。编程

1.问题描述缓存

1.设置的jvm内存过小,对象所需内存太大,建立对象时分配空间,就会抛出这个异常。并发

2.流量/数据峰值,应用程序自身的处理存在必定的限额,好比必定数量的用户或必定数量的数据。而当用户数量或数据量忽然激增并超过预期的阈值时,那么就会峰值中止前正常运行的操做将中止并触发java . lang.OutOfMemoryError:Java堆空间错误框架

2.示例代码jvm

编译如下代码,执行时jvm参数设置为-Xms20m -Xmx20m

以上这个示例,若是一次请求只分配一次5m的内存的话,请求量不多垃圾回收正常就不会出错,可是一旦并发上来就会超出最大内存值,就会抛出内存溢出。

3.解决方法

首先,若是代码没有什么问题的状况下,能够适当调整-Xms和-Xmx两个jvm参数,使用压力测试来调整这两个参数达到最优值。

其次,尽可能避免大的对象的申请,像文件上传,大批量从数据库中获取,这是须要避免的,尽可能分块或者分批处理,有助于系统的正常稳定的执行。

最后,尽可能提升一次请求的执行速度,垃圾回收越早越好,不然,大量的并发来了的时候,再来新的请求就没法分配内存了,就容易形成系统的雪崩。

 

二.java堆内存泄漏

1.问题描述

Java中的内存泄漏是一些对象再也不被应用程序使用但垃圾收集没法识别的状况。所以,这些未使用的对象仍然在Java堆空间中无限期地存在。不停的堆积最终会触发java . lang.OutOfMemoryError。

2.示例代码

当执行上面的代码时,可能会指望它永远运行,不会出现任何问题,假设单纯的缓存解决方案只将底层映射扩展到10,000个元素,而不是全部键都已经在HashMap中。然而事实上元素将继续被添加,由于key类并无重写它的equals()方法。

随着时间的推移,随着不断使用的泄漏代码,“缓存”的结果最终会消耗大量Java堆空间。当泄漏内存填充堆区域中的全部可用内存时,垃圾收集没法清理它,java . lang.OutOfMemoryError。

3.解决办法

相对来讲对应的解决方案比较简单:重写equals方法便可:

 

三.垃圾回收超时内存溢出

一、问题描述

当应用程序耗尽全部可用内存时,GC开销限制超过了错误,而GC屡次未能清除它,这时便会引起java.lang.OutOfMemoryError。当JVM花费大量的时间执行GC,而收效甚微,而一旦整个GC的过程超过限制便会触发错误(默认的jvm配置GC的时间超过98%,回收堆内存低于2%)。

2.示例代码

3.解决方法

要减小对象生命周期,尽可能能快速的进行垃圾回收。

 

四.Metaspace内存溢出

1.问题描述

元空间的溢出,系统会抛出java.lang.OutOfMemoryError: Metaspace。出现这个异常的问题的缘由是系统的代码很是多或引用的第三方包很是多或者经过动态代码生成类加载等方法,致使元空间的内存占用很大。

2.示例代码

如下是用循环动态生成class的方式来模拟元空间的内存溢出的。

3.如何解决元空间的内存溢出呢?

默认状况下,元空间的大小仅受本地内存限制。可是为了整机的性能,尽可能仍是要对该项进行设置,以避免形成整机的服务停机。

  1)优化参数配置,避免影响其余JVM进程

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:若是释放了大量的空间,就适当下降该值;若是释放了不多的空间,那么在不超过MaxMetaspaceSize时,适当提升该值。 

-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。 

除了上面两个指定大小的选项之外,还有两个与 GC 相关的属性: 
-XX:MinMetaspaceFreeRatio,在GC以后,最小的Metaspace剩余空间容量的百分比,减小为分配空间所致使的垃圾收集 。
-XX:MaxMetaspaceFreeRatio,在GC以后,最大的Metaspace剩余空间容量的百分比,减小为释放空间所致使的垃圾收集。

2)慎重引用第三方包

对第三方包,必定要慎重选择,不须要的包就去掉。这样既有助于提升编译打包的速度,也有助于提升远程部署的速度。

3)关注动态生成类的框架

对于使用大量动态生成类的框架,要作好压力测试,验证动态生成的类是否超出内存的需求会抛出异常。

 

五.直接内存内存溢出

1.问题描述

在使用ByteBuffer中的allocateDirect()的时候会用到,不少javaNIO(像netty)的框架中被封装为其余的方法,出现该问题时会抛出java.lang.OutOfMemoryError: Direct buffer memory异常。

若是你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不作clear的时候就会出现相似的问题。

2.示例代码

3.解决办法

若是常常有相似的操做,能够考虑设置参数:-XX:MaxDirectMemorySize,并及时clear内存。

 

六.栈内存溢出

1.问题描述

当一个线程执行一个Java方法时,JVM将建立一个新的栈帧而且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其余数据。

当一个方法递归调用本身时,新的方法所产生的数据(也能够理解为新的栈帧)将会被push到栈顶,方法每次调用本身时,会拷贝一份当前方法的数据并push到栈中。所以,递归的每层调用都须要建立一个新的栈帧。这样的结果是,栈中愈来愈多的内存将随着递归调用而被消耗,若是递归调用本身一百万次,那么将会产生一百万个栈帧。这样就会形成栈的内存溢出。

2.示例代码

3.解决办法

若是程序中确实有递归调用,出现栈溢出时,能够调高-Xss大小,就能够解决栈内存溢出的问题了。递归调用防止造成死循环,不然就会出现栈内存溢出。

 

七.建立本地线程内存溢出

1.问题描述

线程基本只占用heap之外的内存区域,也就是这个错误说明除了heap之外的区域,没法为线程分配一块内存区域了,这个要么是内存自己就不够,要么heap的空间设置得太大了,致使了剩余的内存已经很少了,而因为线程自己要占用内存,因此就不够用了。

3.解决方法

首先检查操做系统是否有线程数的限制,使用shell也没法建立线程,若是是这个问题就须要调整系统的最大可支持的文件数。

平常开发中尽可能保证线程最大数的可控制的,不要随意使用线程池。不能无限制的增加下去。

 

八.超出交换区内存溢出

1.问题描述

在Java应用程序启动过程当中,能够经过-Xmx和其余相似的启动参数限制指定的所需的内存。而当JVM所请求的总内存大于可用物理内存的状况下,操做系统开始将内容从内存转换为硬盘。

通常来讲JVM会抛出Out of swap space错误,表明应用程序向JVM native heap请求分配内存失败而且native heap也即将耗尽时,错误消息中包含分配失败的大小(以字节为单位)和请求失败的缘由。

2.解决办法

增长系统交换区的大小,我我的认为,若是使用了交换区,性能会大大下降,不建议采用这种方式,生产环境尽可能避免最大内存超过系统的物理内存。其次,去掉系统交换区,只使用系统的内存,保证应用的性能。

 

总结

经过以上的几种出现的内存溢出状况,你们在实际碰到问题时也就会知道怎么解决了。对jvm 的工做原理也有必定的深刻理解,面试时问到相关问题,我相信定会给出超出面试人员与其的答案。最后,笔者认为编程和写代码是两回事。如今不少人停留在用代码实现功能的阶段(好比我),对于代码规范、拓展、性能等等方面考虑的不到位,致使后续存在不少问题。咱们使用一项技术尽量的知其然知其因此然,这样才能有一个本质的提高。