哈喽,大家好,本期这一系列,咱们聊一下发生OOM咱们应该怎么处理。
1. Java堆溢出
Java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots
到对象
之间有可达路径
避免垃圾回收机制消除这些对象,那么对象数量到达最大堆容量限制后就会产生内存溢出异常。
要解决这个问题,一般的手段是先通过内存影像分析工具JVM那点事-内存溢出如何处理(1)——MAT工具的下载使用对Dump出来的堆转储快照进行分析。重点是确定内存对象是否是有必要的。也就是要先分清楚到底是出现了内存泄露(Memory Leak
)还是内存溢出(Memory Overflow
)。
如果是内存泄露,可以进一步查看泄露对象到GCRoots
的引用链,于是就能找到泄露对象通过怎么样的路径(也就是dominator ['dɒmɪneɪtə]
支配者)与GC相关联并导致GC无法自动回收它们。
如果不存在泄露,换句话说。就是内存中的对象确实都还必须存活着,那么就需要检查JVM的堆参数(-XMx
和-XMs
)与物理内存对比是否还可以调大。从代码检查角度,判断是否存在某些对象声明周期过长,持有状态时间过长,应尝试减少程序运行期的内存消耗。
2. 虚拟机栈和本地方法栈溢出
栈容量只由-Xss
参数设置。关于虚拟机栈和本地方法栈,java虚拟机描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出
StackOverflowError
([fləʊ]
流动)。 - 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出
OutOfMemoryError
。
操作系统分配给每个进程的内存是有限的(JVM是一个进程)虚拟机提供了参数来控制Java
堆(-Xmx
)和方法区(MaxPermSize
[pɜ:m]电卷发
)内存的最大值,程序计数器销毁内存很小,可以忽略掉。剩下的内存就由虚拟机栈
和本地方法栈
瓜分。
若是建立多线程导致的内存溢出,在不减少线程数的情况下,可以通过减少最大堆和减少栈容量来换取更多的线程。
3. 方法区和运行时常量池溢出
我们可以通过-XX:PermSize
和-XX:MaxPermSize
限制方法区的大小。方法区存放Class
相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。现在很多主流框架,如Spring
、Hibernate
,在对类进行增强时,都会使用CGLib
这类字节码技术,增强的类越多,需要越大的方法区保证动态生成的Class
可以加载入内存。可能会导致方法区内存溢出。
4. 本机直接内存溢出
JVM那点事-JVM内存结构可以了解一下堆外内存。这里简单介绍下:(直接内存大多情况下被称为堆外内存,自从java引入NIO
之后,堆外内存使用越来越普遍,通过native
方法可以分配堆外内存,通过DirectByteBuffer
对象来操纵)。
DirectMemory
容量可以通过 -XX:MaxDirectMemorySize
参数来设置最大可用直接内存,如果启动时未设置则默认为最大堆内存大小,即与 -Xmx
相同。即假如最大堆内存为1G,则默认直接内存也为1G,那么 JVM 最大需要的内存大小为2G多一些。当直接内存达到最大限制时就会触发GC,如果回收失败则会引起OutOfMemoryError
。
若是发生直接内存溢出的情况,解决方案可以扩大堆外内存或者禁止netty使用堆外内存,转用堆内内存。
扩大堆外内存:
-XX:MaxDirectMemorySize=1024m
禁用堆外内存:
-Dio.netty.noPreferDirect=true \
-Dio.netty.leakDetectionLevel=advanced \
这里的操作要看服务器的内存大小,内存足够大,直接扩大堆外内存即可。