Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的,因此Java内存区域的划分也就是JVM的区域划分。Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(运行时数据区),通过类加载器加到运行时数据区来进行存储程序执行期间需要用到的数据和相关信息,在这个数据区中,由以下几部分组成:

java

1. 虚拟机栈

虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每一个栈帧对应一个被调用的方法,方法调用的过程对应着栈帧在JVM中从入栈到出栈的过程。

栈的线程是私有的,执行每一个方法都会相应的创建一个栈帧,放到栈中(入栈),而且是放在栈顶。在方法结束后,栈帧出栈。

1.1 栈帧

用于支持虚拟机进行方法的调用和方法的执行的数据结构,它是虚拟机运行时数据区中的栈空间的栈元素。每个栈帧中包括:

1.1.1 局部变量表

用于存储方法中的局部变量(不包括静态变量和方法形参)。当变量为基本数据类型时,直接存储值,当变量为引用数据类型时,存储指向具体对象的引用。

1.1.1.1 基本数据类型的存储
A. 基本数据类型的局部变量

在方法内定义的变量直接存储在栈中:

1
2
3
4
5
fun(){
int a = 100;
int b = 101;
int c = 100;
}

当我们写 int a = 100; 其实是分为两步:

1
2
int a;//定义变量
a = 100;//赋值

首先JVM创建一个变量a;存在局部变量表里面,然后去栈中查找是否存在有字面量为100的的内容,如果有,就直接把a指向这个地址,如果没有,就在栈中开辟一块空间来存储 “100”这个内容,并且把a指向这个地址。因此我们可以知道:
我们声明并初始化基本数据类型的局部变量时,变量名以及字面量值都是存储在栈中,而且是真实的内容。

这样我们就可以推出int c = 100;的思路:
由于字面量为100的内容已经在栈中存在,所有c是直接指向这个地址的。
栈中当前数据在当前线程下是共享的

总结:
基本数据类型的数据本身是不会改变的,当局部变量重新赋值时,并不是在内存中改变字面量内容,而是重新在栈中寻找已存在的相同的数据,若栈中不存在,就开辟内存来存新数据,并把重新赋值的局部变量的引用指向新数据的地址。

B. 基本数据类型的成员变量
1
2
3
4
5
6
7
8
9
@Getter
@Setter
class Person{
private String name;
private int age;
}

//调用
Person p = new Person();

根据 的概念我们得出:
基本数据类型的成员变量名和值都存储于堆中,其生命周期和对象的是一致的。

1.1.2 操作数栈

Java虚拟机中解释执行引擎被称为 “ 基于栈的执行引擎 ” 其中的栈就是指操作数栈。

1.1.3 方法出口地址

储存方法执行完成后的返回地址。

1.1.4 指向运行时常量池的引用

存储程序执行中可能用到的常量的引用。

1.1.5 一些附加信息

2. 堆

用来存储对象本身和数组,在JVM中只有一个堆,所以堆是被所有的线程共享的。

3. 方法区

是所有线程共享的逻辑区域,在JVM中只有一个方法区,用来存储可以被所有的线程共享的内容,它是线程安全的,多个线程同时访问相同的内容时,只能有一个线程装载该内容,其他线程只能等待。

4. 本地方法栈

本地方法栈跟虚拟机栈的功能基本一致,线程也是私有的,它们的区别是虚拟机栈是为执行Java方法服务的,本地方法栈是为执行本地方法服务的。

5. 程序计数器

线程也是私有的。记录着当前线程所执行的字节码的行号指示器,在程序运行中,字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令, 分支、循环、异常处理、线程恢复、等等基础功能都是需要计数器来完成。

参考:这一次,彻底解决Java的值传递和引用传递