面试高频——JVM内存溢出(OOM)的各种情况

栈溢出

HotSpot版本中栈的大小是固定的,不支持扩展。

Java.lang.StackOverflowError(单个虚拟机栈)一般普通的方法调用是很难出现的,如果遇到了可能写了无限递归

虚拟机栈带给我们的启示:方法的执行因为要打包成栈帧。所以天生比面向过程的简单循环要慢。所以树的遍历算法中递归和非递归(未封装,直接循环来实现)都有其存在的意义。递归的代码更简洁,非递归代码复制但速度快。

栈内存的OOM(此处指的整个运行时数据区的栈空间)的发生条件是:不断地创建线程,JVM会向操作系统不断地申请栈内存,机器没有足够的内存,就会导致直接死机。

注:(1)在java语言中,当创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象,同时创建一个操作系统线程,而这个操作系统线程的内存不是JVMMemory,而是系统中剩下的内存(直接内存,堆外内存)

扩展:(2)当你使用JAVA线程,JVM内会创建一个Thread对象。但是同时也会在操作系统里创建一个真正的物理线程(参考JVM规范),操作系统会在剩下的内存中创建这个物理线程,而不是在JVM中。因此想要更多的创建线程,还要预留充足的堆外内存。

image.png

image.png

堆溢出(内存重点)

内存溢出:申请内存空间超出最大堆内存空间。

如果并非是程序问题,而是我们程序确实大量内存而JVM无法满足我们时,则通过调整堆内存参数解决。

内存泄漏:指长期不使用,却一直无法回收的对象。

如果是内存泄漏,但堆中存储的对象又必须是存活的(使用率极低),那么就应该检查JVM的堆参数设置,与机器内存相比,哪些空间是可调的。再从代码上检查是否存在某些对象生命周期过长,存储结构设计不合理等情况,尽量减少程序运行时的内存消耗。

方法区溢出

(1)运行时常量池溢出(在JDK6之前的永久代中,常量池会出现溢出情况。随着永久代更替为元空间后,我们的方法区常量池移动到了堆内存。因此常量池溢出的情况已经很少见了)

(2)方法区中保存的Class对象没有及时被回收掉,或者Class信息占用的存储超过我们的配置。

  注:回收class的条件非常苛刻。

1,该类的所有实例都已经被回收,也就是堆内存中不再存在任何该类的实例

2,记载该类的ClassLoader已经被回收

3,该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。

本机直接内存溢出

直接内存不是JAVA虚拟机规范中定义的内存区域。在JDK1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,可以使用native函数库直接分配堆外内存(本地方法栈干的事),然后通过抑制存储在JAVA堆中的DirectByteBuffer对象对这块内存的引用进行操作。这样就能在一些场景中显著提升性能,避免了Java堆和Native堆中来回复制数据。

1. 本机直接内存的分配不会受到Java堆大小的限制,收本机总内存大小的限制。

2. 直接内存的大小可以用参数设置

3. 直接内存申请空间耗费更高的性能

4. 直接内存IO读写的性能要优于普通内存

当我们需要频繁访问大的内存而不是申请和释放空间的时候,通过使用直接内存可以提高性能。

使用UnSafe类可以不断地对直接内存申请空间,最终会OOM。

注意:由于申请直接内存不由虚拟机管理,所以由此导致的OOM是不会在Heap Dump文件中看出明显异常。当OOM后发现Dump文件很小同时程序直接或间接的使用了NIO,可以考虑是否是直接内存溢出。

Unsafe

Java中操作直接内存的类,但JDK1.9以后不用了。

猜你喜欢

转载自blog.csdn.net/weixin_47184173/article/details/113576943