Java堆溢出
只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量之后就会产生内存溢出异常
1 public class HeapOOM{ 2 static class OOMObject{ 3 } 4 5 public static void main(String[] args){ 6 List<OOMObject> list = new ArrayList<OOMObject>(); 7 while(true){ 8 list.add(new OOMObject()): 9 } 10 } 11 }
解决堆内的异常,一般手段先通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。
如果是内存泄漏, 可以进一步通过工具查看泄漏对象到GC Roots的引用链,于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们。
如果不存在内存泄漏,就是内存中的对象确实都还必须活着,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
虚拟机栈和本地方法栈溢出
栈容量只由-Xss参数设定,在Java虚拟机规范中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
public class JavaVMStackSOF{ private int stackLength = 1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); try{ oom.stackLeak(); }catch(Throwable e){ System.out.println("stack length:"+oom.stackLength); throw e; } } }
上述是在单线程情况下,无论是由于栈帧太大还是虚拟机栈容量太小,抛出的都是StackOverFlowError。还有一种因为创建线程导致的内存溢出异常,代码如下:
public class JavaVMStackOOM{ private void dontStop(){ while(true){ } } public void stackLeakByThread(){ while(true){ Thread thread = new Thread(new Runnable(){ @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }
方法区和运行时常量池溢出
首先介绍String.intern()这个本地方法,它的作用是:如果字符串常量池中已经包含一个此String对象的字符串,则返回代表池中这个字符串的String对象,否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
我们可通过-XX: PermSize 和 -XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量
public class RuntimeConstantPoolOOM{ public static void main(String[] args) { //使用List保持着常量池引用,避免Full GC回收常量池行为 List<String> list = new ArrayList<String>(); int i = 0; while(true){ list.add(String.valueOf(i++).intern()); } } }