Java的对象内存图

一、Java内存分配介绍

Java 虚拟机(JVM)在执行 Java 程序时会使用多个内存区域

  • 栈:方法运行时所进入的内存,变量也是在这里
  • 堆:new出来的东西会在这块内存中开辟空间并产生地址
  • 方法区:字节码文件加载时进入的内存(class类、main方法等)
  • 本地方法栈
  • 寄存器

1. 堆区(Heap)

  • 用途:
    • 堆区是 Java 中用于动态分配内存的区域。所有的对象实例和数组都在堆中分配。
  • 特点:
    • 堆是共享的,所有线程都可以访问。
    • 垃圾回收器负责自动管理堆内存,回收不再使用的对象。
  • 生命周期:
    • 对象在堆中创建时,只有在没有引用指向该对象时,才会被垃圾回收。

2. 栈区(Stack)

  • 用途:
    • 栈区用于存储局部变量和方法调用的上下文。
  • 特点:
    • 每个线程都有自己的栈,线程之间相互独立。
    • 栈内存采用后进先出(LIFO)原则,方法调用时会将方法的局部变量和参数压入栈中,方法返回时会清除相应的栈帧。
  • 生命周期:
    • 局部变量的生命周期与方法调用的生命周期相同,方法结束后,局部变量会被自动释放。

3. 方法区(Method Area)

  • 用途:
    • 方法区用于存储类的结构信息,包括类的元数据、常量、静态变量和即时编译(JIT)编译后的代码等。
  • 特点:
    • 方法区在 JVM 中是共享的,所有线程都可以访问。
    • 在 HotSpot JVM 中,方法区通常实现为永久代(PermGen)或元空间(Metaspace)。
  • 生命周期:
    • 类的元数据在类被加载时创建,类被卸载时释放。

4. 本地方法栈(Native Method Stack)

  • 用途:
    • 本地方法栈用于支持 Java 中调用的本地方法(Native Method)。本地方法是用其他语言(如 C 或 C++)编写的,并通过 Java Native Interface (JNI) 调用。
  • 特点:
    • 本地方法栈和 Java 栈类似,每个线程都有自己的本地方法栈。
    • 存储本地方法调用的参数、局部变量和返回值。
  • 生命周期:
    • 本地方法栈的生命周期与线程相同,当线程结束时,相关的本地方法栈也会被销毁。

5. 寄存器(Registers)

  • 用途:
    • 寄存器是 CPU 中的高速存储器,用于存储临时数据和指令。JVM 在执行字节码时,常常会使用寄存器来提高执行效率。
  • 特点:
    • 寄存器的数量相对较少,访问速度非常快。
    • JVM 在执行字节码时,将某些运算的结果直接存储在寄存器中,而不是在栈中进行操作。
  • 使用:
    • JVM 的具体实现会利用寄存器进行优化,尤其是在进行算术运算和逻辑运算时。

总结:

Java 虚拟机 在执行 Java 程序时,会在这些不同的内存区域中管理内存:

  • 堆区:存储对象实例和数组,进行动态内存分配。
  • 栈区:存储方法调用的局部变量和参数,支持方法的调用与返回。
  • 方法区:存储类的结构信息和静态数据,支持反射和类的加载。
  • 本地方法栈:用于支持调用本地方法,存储本地方法的局部变量和参数。
  • 寄存器:用于存储临时数据和指令,提高执行效率。

二、一个对象的内存图

示例代码:

public class Student {
    String name;
    int age;
    
    public void study() {
        System.out.println("好好学习");
    }
}
public class TestStudent {
    public static void main(String[] args) {
        Student s= new Student();
        System.out.println(s);
        System.out.println(s.name + "..." + s.age);
        s.name = "阿明";
        s.age = 21;
        System.out.println(s.name + "..." + s.age);
        s.study();
    }
}

在这里插入图片描述

一个对象的内存图的虚拟机执行过程:

  1. 先在方法区加载TestStudent类的字节码文件,将main方法进行临时存储
  2. 此时main方法会被加载到栈里
  3. 在方法区加载Student类文件,临时存储
  4. Student s——在栈区的main方法中开辟以s命名的内存空间
  5. new Student()——在堆区开辟一个空间,并将Student类里面的所有成员变量拷贝过去,并获得所有成员方法的地址
  6. 默认初始化、显示初始化、构造方法初始化成员变量
  7. 将堆中的地址值赋值给栈区的局部变量s
  8. System.out.println(s)——即打印s的地址值
  9. 后面的代码根据s的地址对在堆区的成员变量的值进行操作
  10. s.study()在调用堆区的方法地址,再找的方法区中进行操作,此时该方法会被加载进栈
  11. 进行study方法里的System.out.println("好好学习")操作
  12. study方法执行完毕,退出栈区,main方法也执行完毕,退出栈区
  13. 堆中的内存地址则没地调用,堆的内容也会被系统回收

三、二个对象的内存图的虚拟机执行过程:

和一个对象的内存图相比,方法区不必再次加载,堆区开辟新的空间即可,且两个空间互不影响


四、两个引用指向同一个对象:

示例代码:

public class Student {
    String name;
    int age;
    
    public void study() {
        System.out.println("好好学习");
    }
}
public class TestStudent {
    public static void main(String[] args) {
        Student stu1= new Student();
        stu1.name = "阿明";
        Student stu2 = stu1;
        stu2.name = "阿珍"
        System.out.println(stu1.name + "..." + stu2.name);
        stu1 = null;
        System.out.println(stu1.name);
        System.out.println(stu2.name);
        stu2 = null;
    }
}

不同在Student stu2 = stu1相当于将stu1保存的堆区new出的地址赋值给stu2,共同操作该堆区的空间,stu1 = null——stu1指向的方法消失,但还可以通过stu2操作,但当stu2 = null时堆区的空间不被调用,则会消失,堆区被清空,栈区的方法也操作完毕,出栈


五、基本数据类型和引用数据类型

​ 在编程中,数据类型通常分为基本数据类型(或原始数据类型)和引用数据类型。

基本数据类型

  1. 定义:基本数据类型是语言内置的类型,通常直接表示简单的值。

  2. 值存储:基本数据类型的变量直接存储数据的值。

  3. 内存分配:在栈内存中分配,存储效率高。

  4. 示例:

    • 整数(如 int
    • 浮点数(如 float, double
    • 字符(如 char
    • 布尔值(如 boolean

引用数据类型

  1. 定义:引用数据类型是用户定义的类型,包括对象和数组。

  2. 值存储:引用数据类型的变量存储的是对象的引用(地址),而不是对象本身的值。

  3. 内存分配:在堆内存中分配,通常占用更多内存。

  4. 示例:

    • 类(如 String, ArrayList
    • 接口
    • 数组(如 int[]

主要区别总结

  • 存储方式:基本数据类型存储值,引用数据类型存储地址。
  • 内存管理:基本数据类型在中,引用数据类型在中。
  • 性能:基本数据类型通常更高效,因为它们的内存占用较小。

六、this的内存原理

this的作用:

  1. 区分局部变量和成员变量
  2. 用于构造函数的重载
public class Example {
    private int value;

    public Example(int value) {
        this.value = value; // 指向当前实例的变量
    }

    public Example() {
        this(5); // 调用带参数的构造函数
    }
}

解释

  • 当你创建一个 Example 对象时,可以使用 new Example(10) 创建一个有参数的实例,this.value 将被赋值为 10。
  • 如果使用 new Example(),则会调用无参构造函数,它内部又调用了带参数的构造函数 this(5),因此 value 将被初始化为 5。

this的本质:

所在方法调用者的地址值,管理调用者的数据


七、成员变量和局部变量的区别

1. 定义

  • 成员变量:也称为实例变量,属于类的实例,定义在类的内部但在方法外部。
  • 局部变量:定义在方法、构造函数或代码块内部,只在该特定作用域内有效。

2. 作用域

  • 成员变量:在整个类中可访问,所有实例方法都可以访问,甚至可以通过对象实例访问。
  • 局部变量:仅在定义它们的方法或代码块中有效,超出该作用域后无法访问。

3. 生命周期

  • 成员变量:对象创建时分配内存,直到对象被垃圾回收时,成员变量的生命周期持续存在。
  • 局部变量:在方法或代码块执行时分配内存,方法执行完毕后被销毁。

4. 默认值

  • 成员变量:如果没有显式初始化,成员变量会被默认初始化为其类型的默认值(例如,整型为 0,布尔型为 false,引用类型为 null)。
  • 局部变量:必须显式初始化,未初始化的局部变量无法使用,编译器会报错。

5.内存位置不同

  • 成员变量:堆内存(对象里面)
  • 局部变量:栈内存(方法里面)

6. 访问修饰符

  • 成员变量:可以使用访问修饰符(如 public, private, protected) 来控制访问权限。
  • 局部变量:没有访问修饰符,仅在其所在的方法内有效。
示例:
public class Example {
    // 成员变量
    private int memberVariable = 10;

    public void method() {
        // 局部变量
        int localVariable = 5;

        System.out.println("Member Variable: " + memberVariable); // 可以访问
        System.out.println("Local Variable: " + localVariable);   // 可以访问
    }

    public void anotherMethod() {
        // System.out.println(localVariable); // 编译错误:局部变量在此不可访问
    }
}

此篇内容结束

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐