虚拟机栈
虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。
栈帧
:在每个
Java
方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。
栈帧大体都包含四个区域
:
(
局部变量表、操作数栈、动态连接、返回地址
)
1
、 局部变量表
:
顾名思义就是局部变量的表,用于存放我们的局部变量的(方法中的变量)。首先它是一个 32
位的长度,主要存放我们的
Java
的八大基础数据
类型,一般
32
位就可以存放下,如果是
64
位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的
Object
对象,我们只
需要存放它的一个引用地址即可。
2
、
操作数据栈
:
存放
java
方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的
java
数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的。 操作数栈本质上是 JVM
执行引擎的一个工作
区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的。
3
、 动态连接
:
Java
语言特性多态
。
4
、 返回地址
:
正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<
非栈帧中的
>
来确定)
同时,
虚拟机栈这个内存也不是无限大,它有大小限制,默认情况下是
1M。
![](https://i-blog.csdnimg.cn/blog_migrate/053d841e41b70b0947cf9a82c2b8bb8e.png)
程序计数器
如果一个线程的时间片用完了,或者是其
它原因导致这个线程的
CPU
资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。
因为
JVM
是虚拟机,内部有完整的指令与执行的一套流程,所以在运行
Java
方法的时候需要使用程序计数器(记录字节码执行的地址或行号),如果是遇到本地方法(native
方法),这个方法不是
JVM
来具体执行,所以程序计数器不需要记录了,这个是因为在操作系统层面也有一个程序计数器,这个会记录本地代码的执行的地址,所以在执行 native
方法时,
JVM
中程序计数器的值为空
(Undefined)
。 另外程序计数器也是 JVM
中唯一不会
OOM(OutOfMemory)
的内存区域。
方法区
方法区(
Method Area
)是可供各条线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池(
Runtime Constant Pool
) 字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。
方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。
运行时常量池
运行时常量池(
Runtime Constant Pool
)是每一个类或接口的常量池(
Constant_Pool
)的运行时表示形式,它包括了若干种不同的常量:从编 译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。 运行时常量池是方法区的一部分。运行时常量池相对于 Class 常量池的另外一个重要特征是具备动态性。
在
HotSpot
虚拟机、
Java7
版本中已经将永久代的静态变量和运行时常量池转移到了堆中,其余部分则存储在
JVM
的非堆内存中,而
Java8
版本已经将方法区中实现的永久代去掉了,并用元空间(class metadata
)代替了之前的永久代,并且元空间的存储位置是本地内存。
堆
堆是
JVM
上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。堆一般设置成可伸缩的。随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java
中,就叫作
GC
(
Garbage Collection
)。那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java
类中存在的位置。Java 的对象可以分为基本数据类型和普通对象。
对于普通对象来说,
JVM
会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。对于基本数据类型来说(byte
、
short
、
int
、
long
、
float
、
double
、
char)
,有两种情况。当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。