1. Java虚拟机包含以下几个运行时数据区域,如下图所示
程序计数器:
- 线程私有内存, 可以看作是当前线程所执行的字节码的符号指示器。
- 在虚拟机概念模型里,字节码解释器工作时就是通过这个计数器的值来选取下一条需要执行的字节码指令。
- 此区域在虚拟机规范中没有规定OutOfMemoryError情况。
虚拟机栈:
- 线程私有内存,描述Java方法执行的内存模型。
- 每个方法被执行时都会同时创建一个栈帧,栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机中从入栈到出栈的过程。
- 如果线程请求栈深度过大(例如递归函数忘记设置递归基时),抛出StackOverflowError异常。此外当这部分内存扩展时无法申请到足够的内存时抛出OutOfMemoryError异常。
本地方法栈:
- 与虚拟机栈相似,区别是虚拟机栈为执行Java方法(字节码)服务,本地栈为Native方法服务。
堆:
- 所有线程共享的一块内存区域,在虚拟机启动时创建,用来存放对象实例以及数组。
- 是垃圾收集器主要管理区域。若收集器采用分代收集算法,可以细分为新生代(如Eden、From survivor、To survivor空间等)和老年代。
- 堆内存不够完成实例分配,且无法扩展时抛出OutOfMemoryError异常。
方法区:
- 线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 垃圾收集行为再这个区域比较少出现,主要是针对常量池的回收和对类型的卸载。
- 运行时常量池是方法区一部分,用于存放编译期生成的字面量和符号引用。
- 抛出OutOfMemoryError异常。
2. 直接内存
- 直接内存不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。
- 当使用基于通道与缓冲区的I/O方式时,会使用Native函数库直接分配堆外内存,然后通过存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
- 直接内存受本机内存限制,也会抛出OutOfMemoryError异常。
3. 对象访问的例子
public void method(){
...
Object obj = new Object();
...
}
这句代码在方法体中创建了一个新的对象实例,涉及到Java堆、Java栈,方法区这三个区域。
- “Object obj” 反映到Java栈,作为reference类型数据出现。
- “new Object()” 反映到Java堆,存储Object类型所有实例数据。
- 方法区则存储Object类的相关数据(如对象类型、父类、实现接口、方法 等)。
虚拟机实现对象访问的主流方式有两种: 使用句柄和直接指针, 如下图所示:
参考:深入理解Java虚拟机[JVM高级特性与最佳实践](周志明)