直接内存(Direct Memory)
  直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,它直接从操作系统中分配,因此不受Java堆大小的限制,但是会受到本机总内存的大小及处理器寻址空间的限制,因此它也可能导致OutOfMemoryError异常出现。在JDK1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,可以直接从操作系统中分配直接内存,即在堆外分配内存,这样能在一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制数据。
  当使用超过虚拟机允许的直接内存时,虚拟机会抛出OutOfMemoryError异常,由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常。一般来说,如果发现OOM后Dump文件很小,那应该考虑一下,是不是这块内存发生了溢出。
  内存溢出
  Java堆内存溢出
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
  运行以上代码时,可以增加运行参数-Xms20m -Xmx20m,该参数限制Java堆大小为20M,不可扩展。运行结果如下:
<span style="color: #ff0000;">Exception in thread "main" java.lang.OutOfMemoryError: Java heap space</span>
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:242)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
at java.util.ArrayList.add(ArrayList.java:440)
at HeapOOM.main(HeapOOM.java:17)
  可以看到,在堆内存溢出时,除了会报错java.lang.OutOfMemoryError外,还会跟着进一步提示Java heap space。
  虚拟机栈和本地方法栈溢出
  要让虚拟机栈内存溢出,我们可以使用递归调用:因为每次方法调用都需要向栈中压入调用信息,当栈的大小固定时,过深的递归将向栈中压入过量信息,导致
StackOverflowError:
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
  运行以上代码,输出如下:
  stack length:10828
  <span style="color: #ff0000;">Exception in thread "main" java.lang.StackOverflowError</span>
  at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
  at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
  at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
  可以看到,在我的电脑上运行以上代码,多支持的栈深度是10828层,当发生栈溢出时,会报错java.lang.StackOverflowError。
  方法区溢出
  方法区用于存放Class的相关信息,如类名、访问修饰符、字段描述等,对于这个区域的测试,基本思路是运行时使用CGLib产生大量的类去填充方法区,直到溢出:
public class JavaMethodAreaOOM {
static class OOMObject {
}
public static void main(String[] args) {
while( true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject. class);
enhancer.setUseCache( false);
enhancer.setCallback( new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
}
  运行时增加虚拟机参数:-XX:PermSize=10M -XX:MaxPermSize=10M,限制代大小为10M,后报错为java.lang.OutOfMemoryError: PermGen space。报错信息明确说明,溢出区域为代。
  总结
  本文主要说明Java虚拟机一共分为哪几块内存区域,以及这几块内存区域是否会内存溢出,如果这些区域发生内存溢出报错如何。了解这些知识后,以后遇到内存溢出报错,我们可以定位到具体内存区域,然后具体问题,具体分析。