Java程序在虚拟机自动内存管理的机制的帮助下,不容易出现内存泄露和内存溢出问题,这也就要求程序员需要了解虚拟机处理内存的机制,以解决OOM问题。
运行时数据区域
程序计数器
一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。 每个线程都需要一个独立的程序计数器,各线程间互不干扰,独立存储。 如果线程执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,值为空。 此内存区域是唯一一个在Java虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
Java虚拟机栈
线程私有的,每一个线程都有一个独立的栈,与线程的生命周期相同。 每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机中入栈到出栈的过程。 局部变量表存是一组变量存储空间,用于存放方法参数和方法内部定义的局部变量。在编译时即可确定大小,在运行期不改变。 两种异常:
- StackOverflowError:单个栈的大小大于设定值时,配置-Xss,1.5后64位默认1M,32位默认512K,过大会影响线程的个数
- OutOfMemoryError:无法为线程的栈分配内存空间时,减小单个栈的大小,会影响栈的深度(待验证,如何确定多线程总的栈空间大小)
Java堆
存放对象实例,是线程共享的,Java虚拟机管理的内存中最大的一块,也是GC管理的主要区域。 现在回收器基本采用分代回收算法,所以可以细分为新生代和老年代,进一步可以细分为Eden空间、From Survivor空间、To Survivor空间等。
- OutOfMemoryError:堆中没有足够的内存完成实例分配,且堆也无法再扩展,将会抛出OOM
方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,是各个线程共享的。别名Non-Heap(非堆),用于与堆区分。 方法区可以选择不实现垃圾回收,但是常量池的数据却可以增长,容易导致OOM,所以HotSpot从1.7之后将常量池移出到本地内存。
- OutOfMemoryError:方法区中没有足够的内存完成实例分配,将会抛出OOM,可以通过-XX:PermSize=128M -XX:MaxPermSize=512m配置来调大方法区
直接内存
不归虚拟机管理,但是可能引发OOM问题。 JDK1.4以后引入的NIO可以使用Native方法直接使用本地内存,如果使用内存大于宿主机内存,就会发生OOM。