Java虚拟机管理的内存包括如图所示的运行时数据区域:
下面分别进行介绍:
1)程序计数器(Program Counter Register)
- 占用的内存空间比较小,主要作用就是标识当前线程执行的字节码的行号。字节码解释器的工作就是通过不断改变计数器的值来获取下一条要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖程序计数器。
- 程序计数器在每个线程中都是独立互不影响的,因为Java虚拟机多线程的实现是通过轮流切换并分配时间给每个线程来完成的。每个处理器在一个确定的时刻只会执行一条线程中的指令,切换线程之后需要恢复到正确的执行位置。
- 计数器值的含义:如果正在执行的是Java方法,记录的是字节码指令的地址;如果执行的是Native方法,则计数器值为空(Undefined),该内存区域是唯一一个在虚拟机规范中没有规定任何OutOfMemoryError的区域。
2)Java虚拟机栈(Java Virtual Machine Stacks)
- 线程私有,生命周期与线程相同。
- 含义:描述的是Java方法执行的内存模型。每个方法在执行时会创建一个栈帧(Stack Frame)用来存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从调用开始到执行结束的过程就是栈帧在虚拟机栈中入栈到出栈的过程。
- 局部变量表存放的是int等各种基本数据类型、对象引用以及指向下一条执行地址的returnAddress类型等。
- 64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的类型只占1个。
- 方法运行期间不会改变局部变量表的大小。
- 两种异常情况:StackOverflowError(线程请求的栈深度大于虚拟机允许的深度);OutOfMemoryError(虚拟机动态扩展时无法申请到足够的内存)。
3)本地方法栈(Native Method Stack)
- 与虚拟机栈功能类似,区别:虚拟机栈执行的是Java(字节码)服务;本地方法栈为Native方法服务。
- 抛出的异常:StackOverflowError和OutOfMemoryError。
4)Java堆(Java Heap)
- 所有线程共享,虚拟机启动时创建;
- 用来存放对象的实例:所有的对象实例和数组都要在堆上分配;
- 还可以细分:新生代、老年代;Eden、From Survivor、To Survivor;
- 堆可以处于物理上不连续的内存空间中;
- 异常:OutOfMemoryError,对重没有内存来完成实例分配并且也不能再扩展时。
5)方法区(Method Area)
- 各线程共享;
- 存储内容:已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
- 被称为永久代(但是不严格。。)。
- 限制比较宽松:不需要连续的内存、可以选择固定大小或者可扩展、可以不实现垃圾收集。
- 该区域内存回收的目标:针对常量池的回收和对类型的卸载。
- 异常:OutOfMemoryError,该区域无法满足内存分配需求时。
6)运行时常量池(Runtime Constant Pool)
- 方法区的一部分;
- Class文件内容:类的版本、字段、方法、接口等描述信息和常量池(Constant Pool Table);
- 常量池作用:存放编译期生成的各种字面量和符号引用。
- 动态性:Java并不要求常量一定要在编译器才能产生,运行期间也可以将新的常量放入池中,应用--String.intern()方法
- 异常:OutOfMemoryError,运行时常量是方法去的一部分,类似,无法申请到内存时就抛出异常。
7)直接内存(Direct Memory)
- 不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
- JDK1.4中:引入了NIO(New Input/Output),可以使用Native函数库直接分配堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用来进行操作,避免了再Java堆和Native堆中来回复制数据。