Java虚拟机管理内存包括以下几个运行时数据:
1 程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的
行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式执行的。 每条线程都 需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储(又称“线程私有”)。
java虚拟机规范中没有规定任何OutOfMemoryError内存区域是本地的Native方法,这个计数器则为空(Undefined)。
2 Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有,它的生命周期与线程相同。
每个方法在执行的同时都会创建一个栈帧(stack Frame),用于存储局部变量、操作数栈、动态链接、方法出口等信息。第一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
通常会将Java内存分为堆内存(Heap)和栈内存(Stack)。这里的“栈”就是指虚拟机栈,或者说虚拟机栈中局部变量部分。
局部变量表存放编译期可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress类型(指向了一条字节码指令的地址)
局部变量表所需要的内存空间在编译期间完成分配,其中long和double占两个局部变量空间(Slot),其余数据类型只占用一个。方法运行期间不会改变局部变量表大小。
两种异常状况:
- 抛出StackOverflowError异常:如果线程请求的栈深度大于虚拟机所允许的深度
- 抛出OutOfMemoryError异常:虚拟机可以动态扩展,无法申请到足够的内存时
3 本地方法栈
虚拟机栈执行java方法(字节码)服务, 本地方法栈则为虚拟机使用到的Native方法服务。
4 Java堆
java堆是虚拟机所管理的最大的一块。Java堆是被所有的共享的一块内存区域,在虚拟机启动时创建。此内存区域唯一的目的就是存入对象实例。
5 方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
方法区 <> 永久代。
HotSpot设计团队使用永久代来实现方法区。好处是:HotSpot垃圾回收器能够像管理Java堆一样管理这部分的内存。JDK6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了。JDK7,已经把原本放在永久代的字符串常量池、静态变量等移出。JDK8,完全废弃了永久代的概念。把JDK中永久代还剩余的内容全部移动到元空间。
6 运行时常量
运行时常量(Runtime Constant Pool)是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量波表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内存在类加载后放到方法区的运行时常量池中。
常量不一定只有编译期才能产生。
7 直接内存
Direct Memory 直接内存,不是虚拟机运行时数据的一部分,也不是《Java虚拟机规范》定义的内存区域。JDK1.5新加入 NIO,引入一种基于通道(Channel)与缓冲(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存引用进行操作。避免在Java堆和Native堆中来回复制数据,提高性能。