1、前言
Java程序的最大特点就是不用程序员对内存进行管理,它是由Java虚拟机自动管理的,那么Java虚拟机中的内存是如何组织分布的呢?
2、程序计数器
正在执行的虚拟机字节码指令的地址。
3、Java虚拟机栈
Java方法执行的内存模型。
每个方法在执行的同时都会创建一个栈帧,用以存储:
- 局部变量表,
- 操作数栈,
- 动态链接,
- 方法出口等信息。
每一个方法从调用直至执行完成的过程,都对应这个一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表部分是栈中存储的主要内容,其中存放了:
- 编译器可知的基本数据类型
- 对象引用Reference
- returnAddress类型(指向了一条字节码指令的地址)
局部变量表所需的内存空间在编译期间就确定了,完成了分配,在方法运行期间不会改变局部变量表的大小。
4、本地方法栈
HotSpot将本地方法栈和虚拟机栈合二为一。
5、Java堆
所有的对象实例及数组都在堆上分配。是垃圾收集管理的主要区域。
基本都采用分代回收的算法,新生代,老年代,以及永久代。
从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区TLAB,目的都是为了更好的分配内存与回收内存。
6、方法区
用于存储已被虚拟机加载的以下内容:
- 类信息
- 类的版本
- 字段
- 方法描述信息
- 接口描述信息
- 常量
- 静态变量
- 即时编译器编译后的代码等数据
JDK1.8之前将方法区放入永久代中,方便做内存管理,可是会容易遇到内存溢出的问题(因为永久代中内存回收策略目标主要是针对常量池的回收和对类型的卸载,内存的回收策略很严格,导致内存很少被释放)。
JDK1.8后,将方法区挪入了元空间Metaspace中,元空间内存隶属于本地缓存,不受到Java虚拟机内设置的内存区域xmx的限制。
7、运行时常量池
运行时常量池,是方法区的一部分,用于存放编译器生成:
- 字面量
- 符号引用
这部分内容将在类加载后,进入方法区的运行时常量池存放。除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行期间也可能将新的常量放入池中,比如String类的intern( )方法。
8、本文总结
Java运行时内存区域,以上的描述可以总结为下图,下图中也总结了对应的功能。
各内存区域可能发生的异常情况如下:
程序计数器 | Java虚拟机栈 | 本地方法栈 | Java堆 | 方法区 | 运行时常量池 | 直接内存 | |
---|---|---|---|---|---|---|---|
线程私有 | Y | Y | Y | ||||
是否会发生OutOfMemoryError的异常 | 不会 | Y,也可能发生StackOverflowError异常。 | Y | Y | Y | Y | Y |