因为 Java 虚拟机内存有堆内存, 方法区, 虚拟机栈, 本地方法栈和程序计数器五部分组成, 其中程序计数器是唯一一块不会发生内存溢出异常的内存区, 所以只有四类内存区可能发生内存溢出异常, 其中虚拟机栈和本地方法栈都是 Java 方法执行的内存模型, 所以它们的异常发生情况几乎相同, 另外, 在方法区中. 又有一块内存是常量池, 所以内存溢出的情况可分为 Java 堆溢出, 虚拟机栈和本地方法栈溢出, 方法区和运行时常量池溢三种情况.
一, Java 堆溢出
1, 产生的原因: 因为堆中存放的是对象实例和数组, 所以当对象数量 > 最大堆容量限制时, 就会发生内存溢出异常;
2, 解决方案:
1) 如果对象不是必须的, 但是又有指向 GC Root(后面章节介绍) 的引用链, 此时无法被 GC, 就会出现内存泄露, 可通过定位泄露原因在代码中找到解决方案;
2) 如果对象是必须的, 就要检查虚拟机栈的堆参数能否调大 (当虚拟机内存总容量小于物理内存时可以调大), 如果能调大可通过修改该参数来解决:
--Xmx: 最大堆内存
--Xms: 最小堆内存
如果 --Xmx 和 --Xms 相同, 则说明堆内存不可动态扩展.
二, 虚拟机栈和本地方法栈溢出
1, 发生内存溢出的原因:
由于在 HotSpot 虚拟机中, 不区分虚拟机栈和本地方法栈, 所以设置本地方法栈大小的参数 --Xoss 无效, 一般通过 --Xss 参数设置栈容量 (我猜测 ss 是 stack size 的缩写, 这样比较好记)
虚拟机中定义了两种异常情况:
1) 当线程申请的栈深度超过虚拟机允许的最大栈深度时, 会发生 StackOverflowError 异常;
2) 当栈内存扩展时, 如果不能申请到足够的内存, 就会发生 OutOfMemoryError 异常
我们知道这部分内存是线程私有的, 每个线程都需要分配一块内存, 所以当线程很多时就会发生内存溢出, 下面来分析一下这句话背后的原理:
内存容量 = 堆内存 + 方法区内存 + 程序计数器内存 (可忽略)+ 栈内存 (虚拟机栈和本地方法栈);
因为栈容量在编译器就可知, 且一旦分配在运行期就不会改变, 在栈容量一定的情况下, 每个虚拟机栈分配到的栈容量越大, 可以创建的线程数就越少;
当线程过多时, 就会导致栈容量不足, 从而发生内存溢出;
2, 解决方法:
首先, 判断能不能减少线程数, 如果能则减少线程数; 如果不能减少线程数, 就只能通过减小最大堆内存容量和最大栈容量来解决:
1)--Xmx: 减少
2)--Xss: 减少
三, 方法区和运行时常量池溢出
1, 异常发生原因
方法区主要存储 class 的相关信息, 如类, 名, 访问修饰符, 常量池, 字段描述信息, 方法描述信息等, 所以如果运行时产生大量的类去填满方法区, 就能出现内存溢出异常. 这里就涉及到如何动态产生大量类的方法, 一般有如下两种:
1) 使用反射机制或动态代理
2) 使用 CGLib 直接操作字节码
2, 解决方法:
通过调节方法区大小参数 --XX:PermSize 和 - XX:MaxPermSize 限制方法区大小, 当设置成相同的值时不可扩展.
除以上三种虚拟机内存溢出情况之外, 还有一种本机直接内存溢出, 可通过调节参数 - XX:MaxDirectMemorysize 指定, 若不指定, 则和 Java 堆内存大小一样.
以上就是 Java 虚拟机中的几种内存溢出情况及解决方法.
来源: http://www.bubuko.com/infodetail-2687078.html