Java 内存区域简介

Java 程序在运行时,内存主要分为以下几个区域:

  1. 栈内存(Stack):用于存储局部变量和方法调用的上下文信息。基本类型的变量和引用类型的引用(地址)都存储在栈中。
  2. 堆内存(Heap):用于存储对象实例。当使用 new 关键字创建对象时,对象会被分配在堆中。
  3. 方法区(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. 方法区:存储类的信息(类的结构、方法字节码等)、静态变量和常量池。
  2. 堆内存:存放对象实例和数组。
  3. 栈内存:存储局部变量和方法调用栈帧。

内存图详细分析

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+"]";
	}
  1. 开始

    • 栈内存:main 方法
    • 堆内存:两个 Person 对象
    • 方法区:Person 类
  2. 执行 x1 初始化

    • 堆内存:
      Person@0x123 {
      age=20,
      name="张三",
      flag=1
      }
    • 栈内存:x1 -> 0x123
  3. 执行 x2 初始化

    • 堆内存:
      Person@0x456 {
      age=22,
      name="李四",
      flag=2
      }
    • 栈内存:x2 -> 0x456
  4. 执行 change1 方法

    • 栈内存新增:
      a -> 0x123
      b -> 0x456
      temp -> 0x123
    • 交换后:
      a -> 0x456
      b -> 0x123
    • 方法结束后栈帧销毁,原 x1/x2 引用不变
  5. 执行 change2 方法

    • 栈内存新增:
      a -> 0x123
      b -> 0x456
      temp_age=20
      temp_name="张三"
    • 交换属性后:
      0x123 对象:age=22, name = 李四
      0x456 对象:age=20, name = 张三
    • 方法结束后栈帧销毁
  6. 最终

    • 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 方法则是进入房子里,把房子里的东西进行了交换,那么再次通过钥匙进入房子时,看到的东西就已经改变了。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐