Java虚拟机系列(三)---内存溢出情况及解决方法

时间:2021-07-25 07:54:41

因为Java虚拟机内存有堆内存、方法区、虚拟机栈、本地方法栈和程序计数器五部分组成,其中程序计数器是唯一一块不会发生内存溢出异常的内存区,所以只有四类内存区可能发生内存溢出异常,其中虚拟机栈和本地方法栈都是Java方法执行的内存模型,所以它们的异常发生情况几乎相同,另外,在方法区中。又有一块内存是常量池,所以内存溢出的情况可分为Java堆溢出、虚拟机栈和本地方法栈溢出、方法区和运行时常量池溢三种情况。

一、Java堆溢出

1、产生的原因:因为堆中存放的是对象实例和数组,所以当对象数量>最大堆容量限制时,就会发生内存溢出异常;

2、解决方案:

1)如果对象不是必须的,但是又有指向GC Root(后面章节介绍)的引用链,此时无法被GC,就会出现内存泄露,可通过定位泄露原因在代码中找到解决方案;

2)如果对象是必须的,就要检查虚拟机栈的堆参数能否调大(当虚拟机内存总容量小于物理内存时可以调大),如果能调大可通过修改该参数来解决:

--Xmx:最大堆内存

--Xms:最小堆内存

如果--Xmx和--Xms相同,则说明堆内存不可动态扩展。

二、虚拟机栈和本地方法栈溢出

1、发生内存溢出的原因:

由于在HotSpot虚拟机中,不区分虚拟机栈和本地方法栈,所以设置本地方法栈大小的参数--Xoss无效,一般通过--Xss参数设置栈容量(我猜测ss是stack size的缩写,这样比较好记)

虚拟机中定义了两种异常情况:

1)当线程申请的栈深度超过虚拟机允许的最大栈深度时,会发生*Error异常;

2)当栈内存扩展时,如果不能申请到足够的内存,就会发生OutOfMemoryError异常

我们知道这部分内存是线程私有的,每个线程都需要分配一块内存,所以当线程很多时就会发生内存溢出,下面来分析一下这句话背后的原理:

①内存容量=堆内存+方法区内存+程序计数器内存(可忽略)+栈内存(虚拟机栈和本地方法栈);

②因为栈容量在编译器就可知,且一旦分配在运行期就不会改变,在栈容量一定的情况下,每个虚拟机栈分配到的栈容量越大,可以创建的线程数就越少;

③当线程过多时,就会导致栈容量不足,从而发生内存溢出;

2、解决方法:

首先,判断能不能减少线程数,如果能则减少线程数;如果不能减少线程数,就只能通过减小最大堆内存容量和最大栈容量来解决:

1)--Xmx:减少

2)--Xss:减少

三、方法区和运行时常量池溢出

1、异常发生原因

方法区主要存储class的相关信息,如类,名、访问修饰符、常量池、字段描述信息、方法描述信息等,所以如果运行时产生大量的类去填满方法区,就能出现内存溢出异常。这里就涉及到如何动态产生大量类的方法,一般有如下两种:

1)使用反射机制或动态代理

2)使用CGLib直接操作字节码

2、解决方法:

通过调节方法区大小参数--XX:PermSize和-XX:MaxPermSize限制方法区大小,当设置成相同的值时不可扩展。

除以上三种虚拟机内存溢出情况之外,还有一种本机直接内存溢出,可通过调节参数-XX:MaxDirectMemorysize指定,若不指定,则和Java堆内存大小一样。

以上就是Java虚拟机中的几种内存溢出情况及解决方法。