运行时数据区域
jvm将内存划分成若干个不同的数据区域,各区域各有用途,各自的创建,销毁时间。
jvm运行时数据区示意图
1、程序计数器
较小的一块内存空间,当前线程所执行的字节码的行号指示器。
java多线程是通过线程轮流切换并分配处理器的执行时间来实现,任一时刻,处理器只会执行一条线程中的指令,对于多线程之间的执行位置需要互不影响,因此每条线程需要一个对应的单独的计数器,便于处理器能正确切换到每个线程的执行位置。各计数器互不影响,独立存储。——这一类的内存区域为“线程私有”。
线程执行的是java方法,计数器记录的是虚拟机字节码指令的地址
线程执行的是native方法,计数器为空。
此内存区域是唯一的没有任何OutOfMemoryError情况的区域
总结:线程私有,没有OutOfMemoryError情况。
2,java虚拟机栈
虚拟机栈是java方法执行的内存模型。java方法执行时,会创建一个栈帧(局部变量表,操作数栈,动态链接,方法出口等信息),每个方法从调用到完成的过程,就是一个栈帧在虚拟机栈中,入栈到出栈的过程。
栈帧是方法运行时的基础数据结构
虚拟机栈中需要关注度是局部变量表部分,存放编译期可知的基本数据类型,对象引用,和returnAddress类型。
Reference类型:不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是一个代表对象的句柄或其他与此相关的位置
returnAddress类型:指向一条字节码指令的地址。
局部变量表所需的内存空间,在编译期完成分配,方法在编译期间需要在栈帧中分配的局部变量空间是完全确定的,方法运行期间不会改变局部变量表的大小。
这个区域会出现两种异常:
1,线程请求栈的深度大于虚拟机设定的深度,抛出StackOverFlow异常
2,虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,抛出OutOfMemoryError异常
总结:虚拟机栈的内存是线程私有,关注局部变量表区域,栈帧过程,抛出两种异常:StackOverFlow,OutOfMemoryError
3,本地方法栈
类似与虚拟机栈,区别在与虚拟机执行java方法,本地方法栈执行的是native方法。
有的虚拟机将本地方法栈和虚拟机栈合二为一(Sun HotSpot虚拟机)
也会抛出StackOverFlow和OutOfMemoryError异常。
4,java堆
java堆是jvm所管理的内存中最大的一块区域,被所有线程共享。java堆的唯一目的就是存放对象实例。
The heap is the runtime data area from which memory for all classes instances and arrays is allocated
java堆是一块为所有对象实例以及数组分配内存的运行时数据区域
java堆是GC管理的主要区域,也称为“GC堆”,gc基本采用分代收集算法,因此java堆可划分为新生代和老年代,进一步划分的目的是为了更好的回收内存,更快的回收内存。
java**堆物理可以不连续,只要逻辑连续即可,可扩展**(通过-Xmx,-Xms配置),当堆中没有足够内存来完成实例分配,则抛出OutOfMemoryError错误。
5,方法区
线程之间共享,存储已被虚拟机加载的类信息,常量,静态常量。即时编译器编译后的代码等数据。
在HotSpot虚拟机1.7规范发布之前,GC分代收集扩展至方法区,该区域也被称为“永久代”,这样gc可以管理方法区的这部分内存区域,但是,如此一来容易遇到内存溢出的问题(永久代有-XX:MaxPermSize上限),另外少数方法会因为这个原因,在不同虚拟机下出现不同的表现。(String的intern方法),在Java1.7+ HotSpot中后,把原本放在永久代中的字符串常量池移出。
java方法区物理上可不连续,可扩展,可以不实现垃圾收集。当方法区的内存分配不满足需求时,抛出OutOfMemoryError异常。
运行时常量池
属于方法区一部分。用于存放编译期生成的各种字面量和符号引用。class文件中包含类的版本,字段,方法等信息描述,还包括常量池,常量池的信息将在class加载之后,进入方法区的运行时常量池中存放。
jvm对应运行时常量池没有严格要求,不同的jvm厂商可以自行实现该内存区域。
除了在编译期class文件信息的常量池信息外,运行期间也可以动态的将新的常量放入运行时常量池中,因此具备动态性,该特性利用最多的是String类的intern方法。
当常量池无法申请到足够的内存时,抛出OutOfMemoryError异常。
总结:运行时常量池;具备动态性,可由不同jvm厂商自行实现。
直接内存
不是运行时数据区的一部分,非jvm规范定义的区域,而是单独划分出来的受限与本机内存的一块内存区域。
java NIO的引入,基于channel和buffer的一种I/O方式,使用Native函数直接分配堆外内存,通过在堆中的DirectByteBuffer对象来引用这块内存进行操作,避免了java堆和Native堆中复制数据的过程,提高了性能。
受本机总内存的限制,以及处理器寻址空间的限制。在配置服务器的内存大小参数过程中,往往忽略直接内存的这一块分配空间,因此会导致动态扩展分配直接内存时,出现OutOfMemoryError错误。