JVM那点事-内存溢出如何处理(2)

哈喽,大家好,本期这一系列,咱们聊一下发生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相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。现在很多主流框架,如SpringHibernate,在对类进行增强时,都会使用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 \
这里的操作要看服务器的内存大小,内存足够大,直接扩大堆外内存即可。

你可能感兴趣的:(JVM那点事-内存溢出如何处理(2))