运行时数据区(内存结构)
在类加载器系统将class文件加载到JVM中后,JVM会用一段存储空间来存储执行过程中用到的数据和相关信息,这个数据段被叫做Runtime data area(运行时数据区),运行时数据区分为程序计数器、java方法栈,本地方法栈、堆和方法区五个部分。
程序计数器(PC)
PC是一段较小的内存,用来存放java程序下一条指令所在的地址(如果正在执行的是native方法,则该区域的值为undefined),程序计数器的空间大小不会随着程序的运行而变化,所以该区域不存在内存不足(out of memory)的情况。而且在线程的切换过程中为了每个线程都能不受其他线程的影响,所以每个线程都有一个自己的程序计数器。
虚拟机栈(vm stack)
每个JVM的线程在创建的时候,都会创建一个栈。一个栈包含很多栈桢。JVM的栈好比传统语言C的栈,它维持(存储)本地变量和部分结果,并在方法调用和返回中(被)使用。这个栈是一个后进先出的数据结构,所以当前正在执行的方法在栈的顶端,每当一个方法被调用时,一个新的栈帧就会被创建然后放在了栈的顶端。当方法正常返回或者发生了未捕获的异常,栈帧就会从栈里移除。
栈帧(stack frame)
JVM为每个方法调用创建一个新的栈帧并推到每个方法调用的栈顶。当方法正常返回或者遇到了未捕获的异常,这个栈帧将被移除。栈内存是线程私有的,其生命周期和线程相同。
Stack Overflow Error:
如果栈帧数量过多(n多次调用方法)或某个(些)栈帧过大会导致栈溢出引发SOE(Stack Overflow Error)。如果允许虚拟机栈动态扩展,那么当内存不足时,会导致OOM(OutOfMemoryError)。
栈帧中包含的数据有:局部变量表、操作数栈、返回地址、动态链接以及指向运行时常量池的引用。
- 局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
注:局部变量表以变量槽(solt)为最小单位,一个变量槽最大只允许保存四字节(即:32位)长度的变量。如果超过32位, 则会开辟两个连续的solt。
- 操作数栈
表达式计算在操作数栈中完成。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
- 返回地址
存放调用该方法的PC寄存器的值。
方法结束方式:
- 正常结束
- 出现未处理异常,非正常退出(通过异常完成出口退出的不会给他的上层调用者生产任何的返回值)
- 方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令地址
- 异常退出时,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数,这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接,关于方法的解析与调用看这。所以要执行某个方法时,某个指令(例如invokevirtual)将常量池中的引用作为参数,而根据这个引用就可以找到真正的栈帧。
本地方法栈 (Native Method Stacks)
本地方法栈与虚拟机栈发挥的功能非常类似,只是虚拟机栈为虚拟机执行java方法而服务,而本地方法栈为虚拟机执行native方法而服务。
2.堆(Heap)
虚拟机启动时自动分配创建,用于存放对象的实例,几乎所有对象都在堆上分配内存,当对象无法在该空间申请到内存是将抛出OutOfMemoryError异常。同时也是垃圾收集器管理的主要区域。
2.1 新生代(Young Generation)
类出生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,
结束生命。
新生代分为两部分:伊甸区(Eden space)和幸存者区(Survivor space),所有的类都是在伊甸区被new出来的。幸存区又分为From和To区。当Eden区的空间用完是,程序又需要创建对象,JVM的垃圾回收器将Eden区进行垃圾回收(Minor GC),将Eden区中的不再被其它对象应用的对象进行销毁。然后将Eden区中剩余的对象移到From Survivor区。若From Survivor区也满了,再对该区进行垃圾回收,然后移动到To Survivor区。
2.2 老年代(Old Generation)
新生代经过多次GC仍然存货的对象移动到老年区。若老年代也满了,这时候将发生Major GC(也可以叫Full GC),进行老年区的内存清理。若老年区执行了Full GC之后发现依然无法进行对象的保存,就会抛出OOM(OutOfMemoryError)异常
2.3 元空间(Meta Space)
在JDK1.8之后,元空间替代了永久代,它是对JVM规范中方法区的实现,区别在于元数据区不在虚拟机当中,而是用的本地内存,永久代在虚拟机当中,永久代逻辑结构上也属于堆,但是物理上不属于。
为什么移除了永久代?
参考官方解释http://openjdk.java.net/jeps/122
大概意思是移除永久代是为融合HotSpot与 JRockit而做出的努力,因为JRockit没有永久代,不需要配置永久代。
3.栈(Stack)
Java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表,操作数栈,动态链接,方法出口等信息)不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致
4.本地方法栈(Native Method Stack)
和栈作用很相似,区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行native方法服务。登记native方法,在Execution Engine执行时加载本地方法库
5.程序计数器(Program Counter Register)
就是一个指针,指向方法区中的方法字节码(用来存储指向吓一跳指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计