Java中的变量根据不同的标准可以分为两类,以其引用的数据类型的不同来划分可分为“原始数据类型变量和引用数据类型变量”,以其作用范围的不同来区分可分为“局部变量,实例变量和静态变量”。
根据“Java中的变量与数据类型”中的介绍,“变量是在内存中分配的保留区域的名称。换句话说,它是一个内存位置的名称”,也就是说我们通过这个变量名字就可以找到一个指向这个变量所引用的数据的内存指针,根据变量的类型我们可以知道这个指针之后的几个字节里存储了这个变量所引用的数据。
所以,了解变量在方法区、栈内存、堆内存中的分配要了解两部分内容,一个是“变量在内存中的分配”,另一个是“变量所引用的数据在内存中的分配”。以下简称为“变量分配”与“数据分配”。
原始数据类型变量
原始数据类型变量的“变量分配”与“数据分配”是在一起的(都在方法区或栈内存或堆内存)
引用数据类型变量
引用数据类型变量的“变量分配”与“数据分配”不一定是在一起的
示例代码
class Fruit {
static int x = 10;
static BigWaterMelon bigWaterMelon_1 = new BigWaterMelon(x);
int y = 20;
BigWaterMelon bigWaterMelon_2 = new BigWaterMelon(y);
public static void main(String[] args) {
final Fruit fruit = new Fruit();
int z = 30;
BigWaterMelon bigWaterMelon_3 = new BigWaterMelon(z);
new Thread() {
@Override
public void run() {
int k = 100;
setWeight(k);
}
void setWeight(int waterMelonWeight) {
fruit.bigWaterMelon_2.weight = waterMelonWeight;
}
}.start();
}
}
class BigWaterMelon {
public BigWaterMelon(int weight) {
this.weight = weight;
}
public int weight;
}
内存分配图
同一种颜色代表变量和对象的引用关系
由于方法区和堆内存的数据都是线程间共享的,所以线程Main Thread,New Thread和Another Thread都可以访问方法区中的静态变量以及访问这个变量所引用的对象的实例变量。
栈内存中每个线程都有自己的虚拟机栈,每一个栈帧之间的数据就是线程独有的了,也就是说线程New Thread中setWeight方法是不能访问线程Main Thread中的局部变量bigWaterMelon_3,但是我们发现setWeight却访问了同为Main Thread局部变量的“fruit”,这是为什么呢?因为“fruit”被声明为final了。
当“fruit”被声明为final后,“fruit”会作为New Thread的构造函数的一个参数传入New Thread,也就是堆内存中Fruit$1对象中的实例变量val$fruit会引用“fruit”引用的对象,从而New Thread可以访问到Main Thread的局部变量“fruit”。
以下内容更新于 2021年04月17日 05:38:09
看到很多同学提出疑问,特此更正下之前的理解错误:
内部类(new Thread)中可以访问局部变量(mainThreadLocalPrice、fruit、sweet)是因为内部类会自动生成成员变量(val$mainThreadLocalPrice、val$fruit、val$sweet)来引用局部变量的引用;
在 java8 中可以不显示的将局部变量声明为 final(相关错误: 从内部类引用的本地变量必须是最终变量或实际上的最终变量)
测试结果
Class : miao.app.memoryarea.Fruit [3982074834] == miao.app.memoryarea.Fruit@66d3c617 [3982077873]
Field[0]: constantPrice [3982078152] == 50 [3982089650]
Field[1]: constantName [3982078161] == 水果 [3982074851]
Field[2]: staticPrice [3982078170] == 100 [3982090278]
Field[3]: thin [3982078179] == miao.app.memoryarea.BigWaterMelon@63947c6b [3982077862]
Field[4]: instancePrice [3982078188] == 200 [3982091469]
Field[5]: juicy [3982078197] == miao.app.memoryarea.BigWaterMelon@2b193f2d [3982077876]
Class : miao.app.memoryarea.Fruit$1 [3982094116] == Thread[Thread-0,5,main] [3982094131]
Field[0]: val$mainThreadLocalPrice [3982676303] == 300 [3982677048]
Field[1]: val$fruit [3982676312] == miao.app.memoryarea.Fruit@66d3c617 [3982077873]
Field[2]: val$sweet [3982676321] == miao.app.memoryarea.BigWaterMelon@b676557 [3982092217]
测试代码
/**
* Created by 鲜榨(177421) - 86336991@qq.com on 2021/4/13.
*/
public class Fruit {
final static int constantPrice = 50;
final static String constantName = "水果";
static int staticPrice = 100;
static BigWaterMelon thin = new BigWaterMelon("皮薄", staticPrice);
int instancePrice = 200;
BigWaterMelon juicy = new BigWaterMelon("多汁", instancePrice);
public static void main(String[] args) {
Fruit fruit = new Fruit();
//打印对象字段
printFields(fruit);
int mainThreadLocalPrice = 300;
BigWaterMelon sweet = new BigWaterMelon("超甜", mainThreadLocalPrice);
new Thread() {
@Override
public void run() {
int subThreadLocalPrice = mainThreadLocalPrice;
setPrice(subThreadLocalPrice);
}
void setPrice(int price) {
fruit.juicy.setPrice(price);
fruit.thin.setPrice(price);
sweet.setPrice(price);
//打印对象字段
printFields(this);
}
}.start();
}
}
/**
* 大西瓜
*/
class BigWaterMelon {
//品种
private String name;
//价格
public int price;
public BigWaterMelon(String name, int price) {
this.name = name;
this.price = price;
}
public void setPrice(int price) {
this.price = price;
}
}