java虚拟机在运行java程序的过程中会把它管理的内存划分为若干个不同的数据区域。这些区域都有自己的用途,以及创建和销毁的时间,有的区域随着虚拟机的启动而存在,有的区域则随着用户线程的启动和结束而创建和销毁,虚拟机所管理的数据区域分为如下图几个数据区域;
图上的区域跟所占内存占大小是不成比率的,下面来介绍这些数据区域;
1程序计算器
程序计算器(Program Counter Register) 是一块很小的内存空间,它可以看做是当前线程所执行的字节码行号指标器,字节码计时器工作的时候就是通过改变这个计算器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要这个计算器来完成;
由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间来实现的,在任何一个确定的时刻,一个处理器都只会处理一条线程中的指令,为了线程切换之后能恢复到正确的位置,每条线程都需要一个独立的程序计算器,各线程的程序计算器是相互独立的,互不影响,这类线程的内存区域成为线程的“线程私有内存”区域;
2虚拟机栈
虚拟机栈(Java Virtual Machine Stacks),与程序计算器一样,java虚拟机栈也是线程私有的,它的生命周期与线程一样,虚拟机栈描述的是java方法执行的内存模型,每个方法执行的时候都会创建一个栈帧,用于储存局部变量表,操作数栈,动态链接,方法出口等信息,每个方法从调用到执行结束,在虚拟机中对对应着一个栈帧,从入栈到出栈的过程;
看过一些对内存分配机制的解说,很多人都将内存分堆区和栈区,这种分法是比较粗糙,java内存的分配比这这复杂得多;
局部变量表:用于储存编译期可知的所有基本数据类型和对象引用类型,注意这里说的只是引用不是对象本身,其中64位长度的long和double类型占用两个局部变量空间,其余的都只占用一个局部变量空间,局部变量表所需要的内存空间在编译期的时候就分配完成,当进入一个方法的时候,这个方法需要在帧中分配多大的局部变量空间是完全可以确定的,方法运行期间不会改变局部变量表的大小;
3本地方法栈
本地方法栈(Native Method Stack), 与虚拟机栈发挥的作用其实是非常相似的,他们的区别不过是虚拟机栈是为虚拟机执行java方法(也就是字节码)服务的,而本地方法栈则是为虚拟机使用的Native方法服务的,注意有的虚拟机是将虚拟机栈和本地方法栈合二为一使用的;
4java堆
对于大多数的应用来说,java堆算是虚拟机管理的最大的一块内存区域了,java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存唯一的目的就是存放所有实例对象,几乎所有实例对象都在这里分配内存的,为什么说是几乎而不是全部呢?因为随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配和标量优化技术会导致一些微妙的变化,所有对象都在堆上分配已将不是那么绝对了;
java堆是垃圾处理器主要管理的区域,所以很多时候也叫GC堆(Garbage Collected Heap),可别翻译成垃圾堆哦 哈哈,从内存回收的角度来看,由于现在收集器基本都是基于分代收集算法,所以java对还可以分为新生代和老生代,再细一点又可以分 Eden空间,from,survivor,to等
根据虚拟机规范规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的就可以,就像我们的磁盘空间一样;
5方法区
方法区和java堆一样,是各个线程共享的内存区,它用于已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,虽然java虚拟机规范将方法区划分为堆的一个逻辑部分,但它有一个别名即非堆,目的应该是将它和java堆区分开来;
对于习惯在HotSpot虚拟机上开发,部署的开发者来说,很多人愿意把方法区称为“永久代”,
HotSpot虚拟机
java虚拟机规范对方法区的限制非常松懈,除了和java堆一样不需要连续的内存和可以选择固定大小的或者可扩展外,可以选择不实现垃圾收集;
6运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译器生成的字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中储存;
java虚拟机对Class文件的每一部分(包括常量池)的格式都有严格规定,每一个字节用来储存哪一种数据都要严格按照规范要求才会被虚拟机认可,装载和执行,但对于运行时常量池java虚拟机没有做任何细节上的要求,不同的提供商实现的虚拟机可以根据自己的需要去实现这个内存区域 ,
既然运行时常量池属于方法区的一部分自然受方法区内存的限制,当常量池无法申请到内存的时候就会报OutOfMemoryError异常;