一、JVM内存模型图解
JVM 运行时数据区 (JVM Runtime Area) 其实就是指 JVM 在运行期间,其对JVM内存空间的划分和分配。网上找到两幅图如下所示(个人认为第二个图Native Method Stack应该画在Java Thead模块中):
二、各数据区域介绍
1、栈区
栈分为java虚拟机栈和本地方法栈
- 重点是Java虚拟机栈,它是线程私有的,生命周期与线程相同。
- 每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程。
- 通常说的栈就是指局部变量表部分,存放编译期间可知的8种基本数据类型,及对象引用和指令地址。局部变量表是在编译期间完成分配,当进入一个方法时,这个栈中的局部变量分配内存大小是确定的。
- 会有两种异常*Error和 OutOfMemoneyError。当线程请求栈深度大于虚拟机所允许的深度就会抛出*Error错误;虚拟机栈动态扩展,当扩展无法申请到足够的内存空间时候,抛出OutOfMemoneyError。
- 本地方法栈为虚拟机使用到的本地方法服务(native),也是线程私有的。
2、堆区
- 堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。
- 堆区是gc的主要区域,通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区最要放新创建对象,From survivor 和 To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1。
不过很多文章介绍分为3个区块,把方法区算着为永久代。这大概是基于Hotspot虚拟机划分, 然后比如IBM j9就不存在永久代概论。不管怎么分区,都是存放对象实例。 - 会有异常OutOfMemoneyError
3、方法区
- 被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)
- 垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。
- 常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性,里面可以存放编译期生成的常量;运行期间的常量也可以添加进入常量池中,比如string的intern()方法。
4、程序计数器
- 当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。
- Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。
- 唯一一块Java虚拟机没有规定任何OutofMemoryError的区块
三、数据区域汇总
名称 |
特征 |
作用 |
配置参数 |
异常 |
程序计数器 |
占用内存小,线程私有,生命周期与线程相同 |
大致为字节码行号指示器 |
无 |
无 |
虚拟机栈 |
线程私有,生命周期与线程相同,使用连续的内存空间 |
Java 方法执行的内存模型,存储局部变量表、操作栈、动态链接、方法出口等信息 |
-Xss |
*Error OutOfMemoryError |
java堆 |
线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 |
保存对象实例,所有对象实例(包括数组)都要在堆上分配 |
-Xms -Xsx -Xmn |
OutOfMemoryError |
方法区 |
线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 |
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 |
-XX:PermSize:16M -XX:MaxPermSize:64M |
OutOfMemoryError |
运行时常量池 |
方法区的一部分,具有动态性 |
存放字面量及符号引用 |
|
|
四、延伸:直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现,所以我们放到这里一起讲解。
在JDK 1.4 中新加入了NIO(NewInput/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。
五、延伸:堆与栈的对比
经常有人把Java 内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。
堆很灵活,但是不安全。对于对象,我们要动态地创建、销毁,不能说后创建的对象没有销毁,先前创建的对象就不能销毁,那样的话我们的程序就寸步难行,所以Java中用堆来存储对象。而一旦堆中的对象被销毁,我们继续引用这个对象的话,就会出现著名的 NullPointerException,这就是堆的缺点——错误的引用逻辑只有在运行时才会被发现。
栈不灵活,但是很严格,是安全的,易于管理。因为只要上面的引用没有销毁,下面引用就一定还在,在大部分程序中,都是先定义的变量、引用先进栈,后定义的后进栈,同时,区块内部的变量、引用在进入区块时压栈,区块结束时出栈,理解了这种机制,我们就可以很方便地理解各种编程语言的作用域的概念了,同时这也是栈的优点——错误的引用逻辑在编译时就可以被发现。
栈--主要存放引用和基本数据类型。
堆--用来存放 new 出来的对象实例。
参考:http://blog.csdn.net/u012152619/article/details/46968883