文章目录
- 一、前言
- 二、JVM内存模型
- 1、Java堆
- 2、方法区
- 3、Java栈
- 3.1、局部变量表
- 3.2、操作数栈
- 3.3、动态链接
- 3.4、返回地址
- 4、本地方法栈
- 5、程序计数器
一、前言
本文将详细介绍JVM内存模型,JVM定义了若干个程序执行期间使用的数据区域。这个区域里的一些数据在JVM启动的时候创建,在JVM退出的时候销毁。而其他的数据依赖于每一个线程,在线程创建时创建,在线程退出时销毁。
二、JVM内存模型
在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程:
Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行。在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存。
所以,当我们在谈 JVM 内存区域划分的时候,其实谈的就是这块空间——运行时数据区。
JVM内存模型主要分为五块:Java 堆内存(Heap)、方法区(Method Area)、JVM栈(JVM Stack)、本地方法栈(Native Method Stacks)、程序计数器(Program Counter Register)。
1、Java堆
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。
堆由老年代和新生代组成,新建的对象一般都是在存放在Eden区,当Eden区不够用时,就会就行Minior GC,回收垃圾对象释放内存空间,当垃圾回收后仍然存活的对象就会放到S0和S1其中一块区域,当再次发生Minior GC时,会把Eden区和有对象的S区中的存活对象移动到另外一块空的S区,这时先前有对象的S区又变成空的S区,所以S0和S1是相互转化的,当S区对象达到一定的阈值时,新生代的对象就会向老年代转移,当老年代的对象达到一定阈值时,就会触发Full GC
新生代 = Eden(伊甸园区) + 2个survior区(幸存者区)
- YGC回收之后,大多数的对象会被回收(90%),活着的进入s0
- 再次YGC,活着的对象eden + s0 -> s1
- 再次YGC,eden + s1 -> s0
- 年龄足够 -> 老年代 (15 CMS 6)
- s区装不下 -> 老年代
老年代 - 顽固分子
- 老年代满了FGC Full GC
2、方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1.7之前 方法区的实现是永久代
1.8之后 方法区的实现是元空间
这里有个注意点必须给大家提醒一下,那就是该区域并不会存放Class对象,Class对象是存放在堆中的,方法区存放的类信息是一些代码片段(类似于C语言中的结构体),供我们程序调用时字节码执行引擎解释执行的。
3、Java栈
线程私有,它的生命周期与线程相同。一个线程一个栈,一个方法一个栈帧。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
3.1、局部变量表
局部变量表存放了编译期可知的各基本数据类型(byte、short、int、long、float、double、boolean、char)、对象引用、retrunAddress类型(指向了一条字节码指令的地址)。概况来说局部变量表用于存放方法参数和方法内部定义的局部变量。
3.2、操作数栈
在方法执行过程中,根据字节码指令,往栈里写入数据或提取数据,即入栈/出栈。
数据运算的地方,大多数指令都在操作数栈弹栈运算,然后结果压栈。
操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中临时的存储空间。
3.3、动态链接
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)
jvm虚拟机栈中的动态链接是将符号引用转换成直接引用,因为java具有多态特性,父类引用调用方法时,如果子类重写了该方法,那么实际父类引用是会调用子类方法的,因此是需要在运行时期进行动态调用的,而不是在类加载的解析阶段能够确定的。
3.4、返回地址
方法的返回分为两种情况,一种是正常退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者,一种是异常导致的方法结束,这种情况是不会传返回值给上层的调用方法。
方法的的一次调用就对应着栈帧在虚拟机栈中的一次入栈出栈操作,因此方法退出时可能做的事情包括:恢复上层方法的局部变量表以及操作数栈,如果有返回值的话,就把返回值压入到调用者栈帧的操作数栈中,还会把PC计数器的值调整为方法调用入口的下一条指令。
4、本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
5、程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。用于记录线程执行到哪一行代码。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。