java虚拟机运行时数据区
java虚拟机在执行java程序的过程中会把它管理的内存划分为若干个不同的数据区域。根据《Java 虚拟机规范(Java SE 7版)》规定,Java虚拟机所管理的内存将会包括一下几个运行时数据区域。
程序计数器
程序计数器是一块较小的内存空间,它可以看作是当前线程执行的字节码的行号指示器。
在虚拟机的概念模型,字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
每条线程都有一个独立的程序计数器。为了线程切换后能恢复到正确的执行位置。java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,一个处理器(对于多核处理器来说就是一个内核)都只会执行一条线程中的指令。
如果线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址。如果是native方法,计数器为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
java方法能按照顺序去执行去调用,就是因为有了这个程序计数器。
java虚拟机栈
java虚拟机栈也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型。
每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法对应一个栈帧。
局部变量表存放了各种基本类型、对象引用和returnAddress类型(指向了一条字节码指令地址)。其中64位长度long 和 double占两个局部变量空间,其他只占一个。
规定的异常情况有两种:
1.线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
2.如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常。
本地方法栈
本地方法栈(Native Method Stack)和虚拟机栈所发挥的作用非常相似,它们之间的区别在于:
虚拟机栈是为虚拟机执行java方法(也就是字节码服务)
而本地方法栈则为虚拟机使用到的Native方法服务
本地方法栈也是线程私有的,也会抛出StackOverflowError和OutOfMemoryError
java堆
Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。
java堆的唯一目的就是存放对象实例对象。几乎所有的对象实例都在这里分配。这一点在java虚拟机规范中描述:所有的对象实例以及数组都要在堆上分配,但随着JIT编译器的发展与逃逸分析技术的成熟,所有的对象都分配在堆上也变的不是那样“绝对”了。
java堆是垃圾收集器管理的主要区域,因此很多地方也称为“GC堆”,从内存回收角度看,由于现在收集器基本都采用分代收集算法,java堆中还可以细分:新生代、老年代。新生代再细分可分为Eden空间、From Survivor空间、To Survivor空间。
当前主流的虚拟机的堆空间都是按照可扩展来实现的,通过(-Xmx和-Xms控制)。堆无法扩展时,抛出OutOfMemoryError异常
方法区
方法区(Method Area)与java堆一样,是各个线程共享的内存区域,它用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它有个别名叫做Non-Heap(非堆)
在HotSpot虚拟机上,很多人将方法区称为“永久代”,本质上两者并不等价。方法区是一个抽象的说法,永久代是方法区的具体实现。GC的设计团队选择把GC分代收集扩展至方法区。这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存。
在JDK1.7的HotSpot中,已经将原本放在永久代的字符串常量池移出,jdk1.7,逐步开始抛弃方法区,将字符串常量池移至堆区.这里jdk文档并没有说运行时常量池是否也跟着移到堆区,也就是说运行时常量依然在方法区,永久代仍存在于JDK1.7中
jdk1.8,JVM移除了永久区,取而代之的是元空间(Metaspace) ,也就是将本地内存用来存储.容量取决于是32位或是64位操作系统的可用虚拟内存大小).这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间.
当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后进入方法区的运行时常量池存放。
相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法
jdk1.6及以下版本:它位于永久代-方法区中
jdk1.7,逐步开始抛弃方法区,将字符串常量池移至堆区.这里jdk文档并没有说运行时常量池是否也跟着移到堆区,也就是说运行时常量依然在方法区,永久代仍存在于JDK1.7中
jdk1.8,JVM移除了永久区,这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间.