Java堆溢出
Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清楚这些对象,那么在对象数量到达最大对的容量限制后就会产生内存溢出异常。
package com.xrq.test;
import java.util.ArrayList;
import java.util.List;
/**
* 测试内容:堆溢出
*
* 虚拟机参数:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOverflowTest
{
public static void main(String[] args)
{
List<HeapOverflowTest> list = new ArrayList<HeapOverflowTest>();
while (true)
{
list.add(new HeapOverflowTest());
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8876.hprof ...
Heap dump file created [15782068 bytes in 0.217 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2760)
at java.util.Arrays.copyOf(Arrays.java:2734)
at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
at java.util.ArrayList.add(ArrayList.java:351)
at com.xrq.test.HeapOverflowTest.main(HeapOverflowTest.java:18)
-Java堆内存的OOM异常是实际应用中常见的内存溢出异常情况。当出现Java堆内存溢出时,异常堆中信息"java.lang.OutOfMemoryError"会跟着进一步提示‘“Java heap space”。
解决方案:一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer) 对Dump出来的堆转储快照分析,重点是确认内存中的对象是否是必要,也就是先分清楚是内存泄漏还是内存溢出引起的OOM。
内存泄漏(不再会被使用的对象内存不能被GC):进一步使用工具查看泄漏对象到GC Roots的引用链。掌握了泄漏对象的类型信息及GC Roots引用链的信息,就可以比较准确的定位出泄漏代码的位置。
内存溢出:检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
虚拟机栈和本地方法栈溢出
HotSpot虚拟机中不区分虚拟机栈和本地方法栈,因此栈容量只有-Xss参数设置。关于虚拟机栈和本地方法栈,在虚拟机规范中描述了两种异常:
(1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
(2)如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
package com.xrq.test;
/**
* 测试内容:栈溢出测试(递归调用导致栈深度不断增加)
*
* 虚拟机参数:-Xss128k
*/
public class StackOverflowTest
{
private int stackLength = 1;
public void stackLeak()
{
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable
{
StackOverflowTest stackOverflow = new StackOverflowTest();
try
{
stackOverflow.stackLeak();
}
catch (Throwable e)
{
System.out.println("stack length:" + stackOverflow.stackLength);
throw e;
}
}
}
stack length:1006
Exception in thread "main" java.lang.StackOverflowError
at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:14)
at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
...
StackOverFlowError这个异常,有错误堆栈可以阅读,比较好定位。而且如果使用虚拟机默认参数,栈深度在大多数情况下,达到1000~2000完全没有问题,正常方法的调用这个深度应该是完全够了。但是如果建立过多线程导致的OutOfMemoryError,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减小最大堆容量和减小栈容量来换取更多的线程了。
方法区和运行时常量池溢出
运行时常量池也是方法区的一部分,所以这两个区域一起看就可以了。这个区域的OutOfMemoryError可以利用String.intern()方法来产生。这是一个Native方法,意思是如果常量池中有一个String对象的字符串就返回池中的这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中去,并且返回此String对象的引用。
package com.xrq.test;
import java.util.ArrayList;
import java.util.List;
/**
* 测试内容:常量池溢出(这个例子也可以说明运行时常量池为方法区的一部分)
*
* 虚拟机参数-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class ConstantPoolOverflowTest
{
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
int i = 0;
while (true)
{
list.add(String.valueOf(i++).intern());
}
}
}
Exception in thread "Reference Handler" Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.xrq.test.ConstantPoolOverflowTest.main(ConstantPoolOverflowTest.java:19)
java.lang.OutOfMemoryError: PermGen space
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)
对于HotSpot而言,方法区=永久代,这里看到OutOfMemoryError的区域是“PermGen space”,即永久代,那其实也就是方法区溢出了。注意一下JDK1.7下是不会有这个异常的,while循环将一直下去,因为JDK1.7之后溢出了永久代并采用Native Memory来实现方法区的规划了。