我们都知道JVM有3大核心:类加载系统,运行时数据区,字节码执行引擎。
前面的文章主要是说到类加载系统,今天这个文章我们来说一说运行时数据区。
运行时数据区在jvm内存模型里面占到非常重要的地位。
整体如下图所示:
如上面图所示,运行时数据区被分为5个部分:堆,栈,本地方法栈,方法区,程序计数器。
下面我们就详细说一下这5个部分:
堆:一般情况下new对象都是在堆上面 jvm的栈(线程)里面放的可能就是运行的线程的局部变量(栈是first in last out),每一个线程都会在内存中开辟自己的栈内存 (每一个线程里面方法都有一个栈帧)。
栈:主要存放的为局部变量,但是如果这个局部变量类型为对象的话,此时栈里面存的就是对象指向堆里面的地址。
栈详解:当java程序开始运行主线程的时候(我们暂且理解为main方法),Java虚拟机就会在栈内存的大片空间给当前方法分配一块独立的内存空间用来放当前线程执行过程中用到的局部变量
有图有真相:
如上图所示:jvm在调用main方法的时候会在栈里面开辟一个小的内存空间(栈桢保存局部变量),然后执行mian方法调用compute()方法的时候,还会再开辟一个小的内存空间(栈桢),每一次开辟都是向上累加的过程。也就是说调用一次方法就会开辟一块栈帧空间,每次拿元素的时候只能从栈顶拿(符合栈的数据结构:先进去后出)。栈里面的数据结构和栈的数据结构是一样的(为什么?这个和代码的执行顺序基本上是一致的) 执行完出栈销毁空间。 默认的方法栈的大小1M。
栈内部结构 :每一个方法对应一个栈帧内存空间(比如一个mian方法对于一个线程栈 而main方法里面还有很多方法 每一个方法都会有一个栈帧)
栈内部结构主要有:局部变量表 操作数栈 动态链接 方法出口。
比如上面的程序图中 先将a,b压入局部变量表中 当执行a+b的时候 再将a,b压入操作数栈 最后将a+b的值压入操作数栈,将我们定义的局部变量先压入操作数栈,然后再局部变量表中申请空间再次压入局部变量表中 依次重复 最后将结果irLoad执行+和-等操作是到cpu寄存器里面做计算的。
说到这里就不得不提到3个概念:
操作数栈:存的是程序运行时需要操作的局部变量临时的一块内存空间。
动态链接:是在程序运行的过程中将符号引用转为直接引用的地址。
静态链接:是在程序加载的过程中将符号引用转为直接引用的地址。
符号表示程序里面的方法名称、()等等,当程序运行到这个符号的时候需要解析(说明当程序运行的时候静态方法会去解析(静态链接),但是自己定义的方法将不会去解析) 就是找到符号对应的指令可以总结引用具体的地址。比如mian方法里面还有一个局部变量表 存入的math ----> math存入的是new在堆里面math对象的地址
本地方法栈:是 在java里面基本上是用C语言写的 很多的native修饰的方法(比如synchorized) 在Java里面有很多是调用c或者c++ 就是在jar包里面的.dll文件
方法区:有的人也叫元空间,在jdk1.8之前叫持久代,1.8后叫元空间,其实方法区就是jvm定义的一个抽象的概念,具体的实现是由持久代或者元空间实现的 元空间在jdk1.8之后使用的是直接内存空间(不去占用java虚拟机的内存空间 使用的是物理内存空间(内存条)) 可以将方法区理解为存代码信息的地方。
同时我们也需要知道元空间默认大小为21M,当对象的大小超过这个值就会触发full Gc,在jdk1.8的时候对元空间进行优化。
方法区容量大小自动扩容机制: 假如这次full gc 将原来方法区里面的21M垃圾回收到还有1M回收不了,就会将方法区的容量调小, 如果回收还有18M回收不了了,可能就会将方法区容量调大,也就是小次触发full gc的水平线可能变成30M或者40M
程序计数器:记录jvm正在执行代码的位置(也是每一个线程都有一个)标志着jvm将要执行的行数 ,程序每执行一行程序计数器都会被修改而这个修改是由jvm的字节码执行引擎来做的 因为java多线程 所以设定了这个程序计数器。
jvm内存模型里面:栈,程序计数器是每一个线程独享的,而堆,方法区是所有的线程共享的 共用物理机的内存(内存条)