本节内容参考书籍:深入理解Java虚拟机
另外,我们讲解的虚拟机为java的 HotSpot JVM,其他虚拟机不讲解。
一、堆内存溢出
1、设置eclipse中的相关参数。
-Xms:堆的最小值
-Xmx:堆的最大值 (这两个值设置一样,可避免对自动扩展)
2、相关代码
java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
demo演示:
/**
* 堆溢出
*/
static class OOMObject{
}
public static void main (String[] args){
System.out.println("测试堆溢出");
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
3、结果
4、分析:
一般的手段是先通过内存影像工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)
(1)如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎么样的路径与GC Roots引用链的信息,就可以比较准确地定位出泄漏代码的位置。
(2)如果不存在泄漏,换句话说,就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的对参数(-Xmx -Xms),与物理内存对比看是否还可以调大,从代码上检查:
a)是否存在某些对象生命周期过长
b)持有状态时间过长
c)尝试减少程序运行期的内存消耗
二、虚拟机栈和本地方法栈溢出
1、设置eclipse中的相关参数。
-Xss128k
说明一点:我们java用的HotSpot虚拟机中,并不区分虚拟机栈和本地方法栈。因此,对于HotSpot来说,虽然-Xoss(设置本地方法栈)参数存在,但实际上是无效的,
2、相关代码
/*** 栈溢出
*/
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JVMOutOfMemoryTest outOfMemoryTest = new JVMOutOfMemoryTest();
try {
outOfMemoryTest.stackLeak();
} catch (Throwable e) {
// TODO: handle exception
System.out.println("stack length------------------:"+outOfMemoryTest.stackLength);
throw e;
}
}
3、结果
Java虚拟机中定义了两种规范:
(1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出栈溢出异常
(2)如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出内存溢出异常
4、分析
如果使用虚拟机默认参数,栈深度在大多数情况下达到1000~2000完全没有问题,对于正常的方法调用(包括递归),这个深度应该完全够用。但是,如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64为虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。