概述
在说jvm内存划分之前,先来说下java程序具体的执行流程:
Java源文件经过java编译器编译后变成class字节码文件,
Jvm的classloader加载class文件完成后,交由execution engine执行。
执行引擎执行过程中用到的所有数据和信息,都存储在runtime data area中。
Runtime data area 就是我们常说的JVM内存。
Runtime data area
Runtime data area 都包括什么呢?
《Java虚拟机规范》中规定,运行时数据区,主要包含:本地方法栈(native method stack),java栈(VM stack),程序计数器(programs counter register),堆(heap),方法区(method area)。
《java虚拟机规范》虽然规定了虚拟机应该包含哪些部分,但是并没有具体的实现规定,所以不同的虚拟机会有不同的实现方式。
Runtime data area都存放哪些数据?
1,程序计数器
程序计数器:保存的是下一条执行命令的内存地址。
在得到指令地址后,程序计数器会自动指向下一条指令的内存地址。具体实现视不同是虚拟机而定,这里将的只是概念和程序计数器的作用。
在JVM中,多线程是轮流获取cpu执行权的,所以,每个线程都会有自己的程序计数器。并且他们不会互相干扰。
在JVM规范中,如果当前程序执行的是native方法,程序计数器中的值应该是下一条指令的地址,如果是非native方法,程序计数器中的值应该是undefined。
由于程序计数器中存储的数据占用的空间,不会随程序的执行发生改变,所以程序计数器不会发生内存溢出现象。
2,java栈
Java栈也叫虚拟机栈,也就是我们常说的栈,java栈是java方法执行的内存模型。
Java栈中存放的是一个个的栈帧,每个栈帧包括:局部变量表,操作数栈,运行时常量池的引用,方法返回地址,附加信息。
当线程调用一个方法的时候,就会创建一个栈帧,并且进行压栈操作。方法执行完毕后出栈。由此可知,线程当前执行的方法一定是在栈顶的。到这里大家应该明白了,为什么递归的时候,一个不注意就会栈溢出了。栈这部分空间对程序员而言是不透明的,完全由系统自动实施。
栈帧中的各部分都是什么呢?
局部变量表
局部变量表:存储方法中的局部变量,局部变量分基本数据类型和引用数据类型,
针对基本数据类型的变量,存储的是值(每种基本数据类型都有固定的内存占用大小),引用数据类型的变量存储的是对象的引用。局部变量表的大小,在编译器时期就可以确定,所以程序执行期间,局部变量表的大小是不会发生改变的。
操作数栈:线程执行方法的过程,实际上就是执行语句的过程,归根到底就是计算机计算的过程。然而程序中所有的计算都是借住于操作数栈来完成的。可以理解成,它就是用来做计算的。
运行时常量池的引用:方法执行过程中可能会用到类中的常量,必须要有一个引用指向运行时常量。
方法返回地址:方法执行完成后,要返回之前调用它的地方,因此栈帧中必须保存方法的返回地址。
附加信息:java虚拟机规范允许一些虚拟机的具体实现增加一些规范中没有描述的信息到栈帧中,例如与调试相关的信息,这部分信息完全取决于虚拟机的具体实现。
3,本地方法栈
本地方法栈和java栈的原理和作用是非常相似的。区别是值java栈是为执行java方法执行服务的。本地方法栈是执行为native方法服务的。JVM规范中并没有对本地方发展的具体实现和数据接口做强制规定,虚拟机的具体实现可以自由实现它。比如hotSopt虚拟机中的实现就是将本地方法栈和java栈合二为一。
4,堆
Java中的堆是用来存储对象本身和数组的(数组的指针在java栈中)。
堆这部分空间是java垃圾回收的主要区域。
堆是被所有线程共享的,在JVM中只有一个堆。
5,方法区
它和堆一样是所有线程共享的。
方法区中主要存储了每个类的信息(比如类的名称,方法信息,字段信息),静态变量,常量,编译器编码后的代码等等。
常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值外,还包含一些以文本形式出现的符号引用(运行时常量池),比如:
类和接口的全限定名;
字段的名称和描述符;
方法和名称和描述符。
补充:
在class文件中除了类的 字段,方法,接口等描述,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。