java——内存图
Person类:包含普通属性(agearrname)和静态属性(heightbrrplay),还有普通方法(run)和静态方法(eattest类:包含普通属性(a)和静态属性(b),有静态方法(fly)和普通方法(jump),在main方法里对Person类和test类的方法和属性进行了调用。
Java 内存区域简介
Java 程序在运行时,内存主要分为以下几个区域:
- 栈内存(Stack):用于存储局部变量和方法调用的上下文信息。基本类型的变量和引用类型的引用(地址)都存储在栈中。
- 堆内存(Heap):用于存储对象实例。当使用
new关键字创建对象时,对象会被分配在堆中。 - 方法区(Method Area):用于存储类的信息、静态变量、常量池等。字符串常量池就位于方法区中。
基本类型的内存图示例:
package 内存图;
public class Person {
// 属性
// 普通属性
public int age;
public int[] arr;
public String name="张三";
// 静态属性
public static int height=210;
public static int[] brr= {1,2,3,4};
public static String play="shot";
// 方法
// 普通方法
public void run() {
System.out.println(play);
}
// 静态方法
public static void eat() {
System.out.println(height);
}
}
package 内存图;
public class test {
public int a=10;
public static int b=10;
public static void main(String[] args) {
Person.eat();//staric直接就能调用
Person aa=new Person();//普通的需要new ,通过对象.方法所有方法都能获取
System.out.println("aa,age:"+aa.age);
aa.name="zzz";
System.out.println("aa.name:"+aa.name);
aa.run();
aa.eat();
// ----------------------------
// 静态直接调
fly();
System.out.println(b);
test test=new test();
System.out.println(test.a);
test.jump();
}
// 静态方法
public static void fly() {
System.out.println("fly");
}
//普通方法
public void jump() {
System.out.println("jump");
}
}
代码功能概述
Person 类:包含普通属性(age、arr、name)和静态属性(height、brr、play),还有普通方法(run)和静态方法(eat)。
test 类:包含普通属性(a)和静态属性(b),有静态方法(fly)和普通方法(jump),在 main 方法里对 Person 类和 test 类的方法和属性进行了调用。
内存图:

内存区域划分
Java 程序运行时的主要内存区域有:
- 方法区:存储类的信息(类的结构、方法字节码等)、静态变量和常量池。
- 堆内存:存放对象实例和数组。
- 栈内存:存储局部变量和方法调用栈帧。
内存图详细分析
1. 类加载阶段
方法区:
加载 Person 类和 test 类的信息,包含类的结构、方法字节码等。为 Person 类的静态属性分配内存并初始化:height = 210,brr 指向一个包含 {1, 2, 3, 4} 的数组,play = "shot"。为 test 类的静态属性分配内存并初始化:b = 10。字符串常量池里存储 "张三"、"shot"、"fly"、"jump" 等字符串常量。
2. main 方法执行阶段
栈内存
为 main 方法创建栈帧,在栈帧里存储局部变量。调用 Person.eat() 时,在栈中为 eat 方法创建栈帧,执行完后栈帧销毁。创建 Person 对象 aa,在栈中存储 aa 这个引用变量,它指向堆中的 Person 对象。调用 test.fly() 时,在栈中为 fly 方法创建栈帧,执行完后栈帧销毁。创建 test 对象 test,在栈中存储 test 这个引用变量,它指向堆中的 test 对象。调用 test.jump() 时,在栈中为 jump 方法创建栈帧,执行完后栈帧销毁。
堆内存:
创建 Person 对象 aa,对象包含普通属性 age(初始值为 0)、arr(初始值为 null)、name(初始值为 "张三")。创建 test 对象 test,对象包含普通属性 a(初始值为 10)。
交换了参数的引用,但原变量不受影响
package com.qcby.内存图2;
public class Test {
public static void main(String[] args) {
Person x1 = new Person();
x1.age = 20;
x1.name = "张三";
x1.flag = 1;
Person x2 = new Person();
x2.age = 22;
x2.name="李四";
x2.flag = 2;
System.out.println(x1);
System.out.println(x2);
change1(x1,x2);
System.out.println(x1);
System.out.println(x2);
change2(x1,x2);
System.out.println(x1);
System.out.println(x2);
}
public static void change1(Person a,Person b) {
Person temp = a;
a=b;
b=temp;
}
public static void change2(Person a,Person b) {
int temp_age = a.age;
String temp_name = a.name;
a.age = b.age;
a.name = b.name;
b.age = temp_age;
b.name = temp_name;
}
}
package com.qcby.内存图2;
public class Person {
public int age;
public String name;
public static int flag;
public void m1() {
}
public static void m2() {
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + " flag = "+flag+"]";
}
-
开始
- 栈内存:main 方法
- 堆内存:两个 Person 对象
- 方法区:Person 类
-
执行 x1 初始化
- 堆内存:
Person@0x123 {
age=20,
name="张三",
flag=1
} - 栈内存:x1 -> 0x123
- 堆内存:
-
执行 x2 初始化
- 堆内存:
Person@0x456 {
age=22,
name="李四",
flag=2
} - 栈内存:x2 -> 0x456
- 堆内存:
-
执行 change1 方法
- 栈内存新增:
a -> 0x123
b -> 0x456
temp -> 0x123 - 交换后:
a -> 0x456
b -> 0x123 - 方法结束后栈帧销毁,原 x1/x2 引用不变
- 栈内存新增:
-
执行 change2 方法
- 栈内存新增:
a -> 0x123
b -> 0x456
temp_age=20
temp_name="张三" - 交换属性后:
0x123 对象:age=22, name = 李四
0x456 对象:age=20, name = 张三 - 方法结束后栈帧销毁
- 栈内存新增:
-
最终
- x1: Person@0x123(age=22, name = 李四,flag=2)
- x2: Person@0x456(age=20, name = 张三,flag=2)
在change1方法中,a和b是x1和x2引用的副本。方法内部对a和b引用的交换,仅仅是在方法内部对这两个副本引用的操作,不会影响到main方法里的x1和x2引用。
在 change2 方法内部,通过 a 和 b 这两个引用副本去访问并修改它们所指向对象的属性。由于 a 和 x1 指向同一个对象,b 和 x2 指向同一个对象,所以对 a 和 b 所指向对象属性的修改,实际上就是对 x1 和 x2 所指向对象属性的修改。
可以把对象想象成房子,引用变量(如 x1、x2、a、b)想象成房子的钥匙。change1 方法就像是把两把钥匙在手中交换了一下,但是房子本身没有任何改变,原来拿着哪把钥匙能开哪间房,现在还是如此。而 change2 方法则是进入房子里,把房子里的东西进行了交换,那么再次通过钥匙进入房子时,看到的东西就已经改变了。
更多推荐


所有评论(0)