java虚拟机在执行java程序的过程当中将它所管理的内存划分为如下几个运行时数据区域:java
线程私有区域(程序计数器、虚拟机栈、本地方法栈),线程共享区域(堆、方法区),直接内存。线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 建立/销毁(在 Hotspot VM 内)。线程共享区域随虚拟机的启动/关闭而建立/销毁。直接内存并非 JVM 运行时数据区的一部分。算法
程序计数器是当前线程执行的字节码的行号指示器,为了线程切换后可以恢复到正确的执行位置,每条线程都须要一个独立的程序计数器,个线程之间的程序计数器互不影响,各自独立存储。这类内存区域为“线程私有”的区域。此内存区域是惟一一个没有OutOfMemoryError的区域。数组
虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存储局部变量表、操做数栈、动态连接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈容量只由-Xss参数设定jvm
/** * VM Args: -Xss160k * -Xss的最小值为160k **/ 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 : 772 Exception in thread "main" java.lang.StackOverflowError at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11) at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
本地方法栈与虚拟机栈的做用类似,区别在于虚拟机方法栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用Native方法服务。Hotspot虚拟机将本地方法栈和虚拟机方法栈合二为一。开发工具
Java堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。全部建立的对象和数组都存放在堆上。Java堆是垃圾收集器管理的主要区域,垃圾收集器主要基于**分代收集算法**,所以从GC的角度对堆进行划分:新生代(Eden空间、From Survivor空间、To Survivor空间)、老年代。若是在堆中没有内存完成示例分配、而且堆也没法再扩展,将会抛出OutOfMemoryError异常。堆的大小能够经过-Xmx(jvm运行时堆的最大值)和-Xms(jvm运行时堆的最小值)控制。测试
如下代码测试堆的OutOfMemoryError异常,将堆的最小值-Xms参数与最大值-Xmx参数设置为同样便可避免堆自动扩展,参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出是Dump出当前的内存堆转储快照以便时候进行分析。spa
/** * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError **/ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while (true) { list.add(new OOMObject()); } } }
输出以下:线程
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid3421.hprof ... Heap dump file created [27644178 bytes in 0.129 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at HeapOOM.main(HeapOOM.java:18)
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。code
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池。java虚拟机对class文件每一部分的格式都有严格的规定,每个字节用于存储哪一种数据都必须符号规范上的要求才会被虚拟机承认、装载和执行。
既然运行时常量池是方法区的一部分,天然受到方法区内存的限制,当常量池没法再申请到内存时会抛出OutOfMemoryError的异常,测试方法区和运行时常量池溢出:
/** * jdk 1.8及以后,VM Args: -XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m * jdk 1.8及以前,VM Args: -XX:PermSize=2m -XX:MaxPermSize=2m **/ public class RuntimeConstantPoolOOM { public static void main(String[] args) { List<String> list = new ArrayList<>(); int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } } }
程序输入:
Error occurred during initialization of VM
java.lang.OutOfMemoryError: Metaspace
<<no stack trace available>>
直接内存(Direct Memory)并非虚拟机运行时的数据区的一部分,也不是java虚拟机规范中定义的内存区域,不会受到java堆大小的限制,不受jvm gc的管理。这部份内存若是频繁使用,也可能致使OutOfMemoryError异常, 测试本机直接内存溢出,DirectMemory容量可经过-XX:MaxDirectMemorySize设置,若是不指定,默认与Java堆最大值(-Xmx指定)同样
/** * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M **/ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true){ unsafe.allocateMemory(_1MB); } } }
经过测试,始终没有抛出OutOfMemoryError异常,测试环境jdk1.8, 开发工具IDEA, 目前还不知道为何,若是有哪位朋友知道为何,你们一块儿讨论下