Java内存区域的划分

根据存放数据的不同,JVM的内存空间分为程序计数器、虚拟机栈、本地方法栈、堆、方法区、运行时常量池、直接内存(在Java运行时内存空间之外),本节总结了这几个区域的作用、特点,并给出了部分区域内存溢出的简单用例。

备注:代码可从 https://github.com/elric2011/jvm-study 获取

程序计数器

程序计数器可理解为当前线程所执行字节码的行号指示器,用于告诉字节码解释器下一条需要执行的字节码指令

程序的逻辑跳转(分支、循环、跳转、异常处理、线程恢复等)都是基于程序计数器实现的

特点:

  • 线程私有
  • 当线程执行到Native Method时,改计数器值会置为空
  • 规范中没有规定该区域的OutOfMemoryError情况

虚拟机栈

虚拟机执行Java方法(字节码)时,用于存放局部变量表、操作数表、方法出口、动态链接等信息的区域。

每一个Java方法被调用到方法调用完成,就对应着一个栈帧在虚拟机栈中的压栈到出栈。

一个方法的局部变量表(存放基础类型和引用类型)大小在编译阶段就可知,进入方法时分配给这块的内存大小是确定。

特点:

  • 线程私有的
  • 当栈深度超出虚拟机允许的深度时,抛出StackOverflowError
  • 如果栈总空间可动态扩展,当扩展时无法申请到足够内存,抛出OutOfMemoryError

代码示例

栈溢出示例:

/**
 * VM Args: -Xss128K
 * @author elric.wang
 */
public class VMStackSOF {

    private int counter = 1;

    private void stackLeak() {
        long a = 0;
        counter++;
        stackLeak();
    }

    public static void main(String[] args) {
        VMStackSOF sut = new VMStackSOF();
        try {
            sut.stackLeak();
        } catch (Throwable t) {
            t.printStackTrace();
            System.out.println("StackLength:" + sut.counter);
        }
    }
}

问题:多线程程序,总的栈空间多大?

本地方法栈

概念与虚拟机栈相同,区别是为虚拟机执行Native Method服务

特点:

  • 线程私有
  • 当栈深度超出虚拟机允许的深度时,抛出StackOverflowError
  • 如果栈总空间可动态扩展,当扩展时无法申请到足够内存,抛出OutOfMemoryError
  • 某些虚拟机实现直接将其与虚拟机栈合二为一(HotSpot)

Java堆

存放对象示例和数组,几乎所有的对象示例都存储在这块区域,所以一般在虚拟机运行时内存中占最大一块比例

特点:

  • 线程间共享
  • 垃圾回收器主要工作于该区域
  • 虚拟机实现时可固定大小,也可以是动态扩展
  • 如果堆中没有足够空间完成实例分配,且无法继续动态扩展时,抛出OutOfMemoryError

代码示例

堆溢出示例:

/**
 * VM Args: -Xmx100M -Xms100M
 * @author elric.wang
 */
public class HeapOOM {

    public static void main(String[] args) {
        List<HeapOOM> list = new ArrayList<HeapOOM>();
        while (true) {
            list.add(new HeapOOM());
        }
    }
}

方法区

用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpot实现中该区域也叫“永久区”,即虚拟机将GC分代收集扩展到该区域。

特点:

  • 线程间共享
  • 当方法区无法满足分配需求时,抛出OutOfMemoryError

运行时常量池

该区域是方法区的一部分,用于存放编译时生成的字面量和符号引用以及运行时产生的常量(例如String.intern()方法产生新的字符串常量)。

特点:

  • 方法区一部分,无法申请到内存空间时抛出OutOfMemoryError

代码示例

运行时常量池溢出示例:

/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 * @author elric.wang
 */
public class RuntimeConstPoolOOM {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            list.add(("do something to make this string longer" + i++).intern());
        }
    }
}

直接内存区

Java在引入NIO后,增加了管道和缓冲区,允许通过Native函数库直接分配堆外内存,以便提升数据交换性能(否则需要在Java堆和Native堆来回复制数据)

特点:

  • 不属于虚拟机运行时内存区
  • 受操作系统内存限制,当无法分配到足够的内存时,抛出OutOfMemoryError

JVM内存分配相关参数

参数 用途 示例 备注
-Xmx 设置堆最大空间 -Xmx256M  
-Xms 设置堆最小空间 -Xms128M  
-Xss 设置每个线程虚拟机栈空间大小 -Xss128K  
-Xoss 设置每个线程本地方栈空间大小 -Xoss128K HotSpot改参数无效
-Xmn 设置年轻态堆空间大小 -Xmn128M eden+2survivor
-XX:PermSize 设置持久代(perm gen)初始值 -XX:PermSize=64M 即方法区
-XX:MaxPermSize 设置持久代最大空间 -XX:MaxPermSize=64M  


blog comments powered by Disqus

Published

16 April 2014

Tags