在Java中,JVM内存模型主要分为堆,方法区,程序计数器,虚拟机栈,本地方法栈,如下图
今天我们就分别讲解一下这几个区域的作用
堆
堆是JVM内存中最大的一块内存,该内存是被多有线程共享,几乎所有的对象和数组都在堆上分配,堆被划分为新生代和老年代,新生代又分为Eden和Survivor区,最后Survivor分为From Survivor和To Survivor组成
java6中,永久代放在非堆内存去,java7把永久代的静态变量和运行时常量放在了堆中,java8永久代被元空间代替了,结构如下图
程序计数器
程序计数器是一块很小的内存空间,主要记录了线程的字节码资质,如分支,循环,跳转,异常,线程恢复等会依赖程序计数器
比如,在多线程中,当线程的数量多于CPU的数量的时候,线程之间就会发生CPU抢占,比如一个线程的时间片用完了,或者其他原因此线程的CPU资源被提前抢夺,那么这个线程退出就需要一个程序计数器记录下一次被唤醒的指令,且是唯一一个没有规定任何OutOfMemoryError情况的区域.
方法区
很多开发者都习惯将方法区成为永久代,但是这两者并不等价。
HotSpot虚拟机使用永久代实现方法区,但是在其他虚拟机中,例如Oracle的JRockit,IBM的J9就不存在永久代的说法,可以说,HotSpot虚拟机中,设计人员使用永久代实现JVM内存模型的方法区。
方法区是用来存储加载类的相关信息,包括类信息,运行时常量,字符串常量池,类信息包括类的版本,字段,方法,接口和父类信息
JVM在执行某个类的时候,必须经过加载,连接,初始化,而连接又分为验证,准备,解析三个阶段,在加载类的时候,JVM会先加载class文件,而在class文件中除了有类的版本,字段,方法和接口等描述信息,还有一项信息就是常量池,为常量池存放的是字面量和符号引用
同时类加载到内存之后,JVM将class文件的常量池放到了运行时常量池,在解析阶段,JVM会把符号引用替换为直接引用
例如,类中的一个字符串常量在class文件中,存放在class文件常量池中,在类加载完之后,JVM把这个字符串常量放到了运行时常量池,而在解析阶段,会指定该字符串对象的索引值,运行时常量是共享的,所以class文件中常量池多个相同的字符串在运行时常量池只有一份.
运行池常量是方法区的一部分,运行时常量相对于class中的常量池有一个另外的特性,就是具备动态性,java语言并不要求常量一定在编译期才会产生,运行期间也可以将新的常量放入池中,比如我们使用String.intern().
方法区和堆一样是线程共享的,因此如两个线程同时访问方法区的同一个类信息,而这个类还没有装入JVM,那么只有一个线程允许加载他,另外一个等待
同时在java7中已经把静态变量和运行池常量放到了堆中,其他部分存储在JVM的非堆内存中,但是在java8版本,使用元空间替代了永久代,除了静态变量和运行时常量还放在堆中,其余在方法区的信息都迁移到了元空间,而元空间是本地内存.
为什么用元空间替换永久代
虚拟机栈
java虚拟机是线程私有的内存空间,他和java线程一起创建,和销毁,当一个线程创建的时候,会在虚拟机栈中申请一个线程栈,存放方法的局部变量,操作数栈,动态链接方法和返回地址等信息,并参与方法的调用和返回,每一个方法的调用伴随着入栈,调出伴随着出栈
本地方法栈
本地方法栈和虚拟机栈功能类似,但是他是管理本地方法的调用,java虚拟机用于管理java函数的调用,且本地方法栈是有c语言实现,而虚拟机栈是有java实现