java内存区域

Java虚拟机(JVM)中的内存区域可以分为以下几个部分:

  1. 方法区(Method Area)

    • 存储类的元数据,例如类名、父类名、方法和变量的信息等。
    • 还存储了静态变量。
    • 运行时常量池就是部分方法区,它包含编译期生成的各种字面量和符号引用。
    • 从逻辑上说,它属于堆的一部分,但是它的垃圾收集行为与堆的其他部分并不完全相同。
  2. 堆(Heap)

    • 存储对象实例。
    • 是垃圾收集器工作的主要区域(也称为“GC堆”)。
    • 可以进一步细分为:年轻代(Young Generation)和老年代(Old Generation/Tenured Generation)。
      • 年轻代包括:
        • Eden区:大部分对象首次分配的地方。
        • 两个Survivor区(S0和S1):存放从Eden区存活下来的对象。
      • 老年代:长时间存活的对象最终会被移动到这里。
  3. Java栈(Java Stack)

    • 每个线程有一个私有的Java栈,它存放“栈帧”。
    • 每个方法调用产生一个新的栈帧;栈帧包括局部变量、操作数栈、常量池引用等信息。
    • Java栈支持方法调用和局部变量的存储。
    • 这个区域是线程私有的,生命周期与线程相同。
  4. 本地方法栈(Native Method Stack)

    • 类似于Java栈,但它为Java的native方法服务。
    • 本地方法是用其他语言(如C)写的方法。
  5. 程序计数器(Program Counter Register)

    • 也是线程私有的。
    • 存储当前线程执行的字节码的行号指示器。
    • 通过改变这个指示器的值,可以选择下一个要执行的指令。
    • 如果执行的是Java方法,这个计数器的值是当前指令的地址;如果执行的是native方法,那么计数器的值是未定义的。

注意

  • 直接内存(Direct Memory):不是虚拟机运行时数据区的一部分,但也可能受到Java应用的使用。它是通过ByteBuffer.allocateDirect()方法分配的,并且是在Java堆之外的。

对于Java内存管理,重要的是理解对象何时和如何被创建、如何存活,以及如何被垃圾收集。不同的垃圾收集策略和算法可能会影响对象如何从年轻代移动到老年代,以及什么时候被清理。

public class MemoryDemo {

    // 静态变量,存储在方法区
    static String staticVar = "Static Variable";

    // 实例变量,存储在堆上的对象内
    String instanceVar = "Instance Variable";

    public static void main(String[] args) {
        // 局部变量,存储在Java栈上
        int localVar = 10;

        // 创建对象,存储在堆上
        MemoryDemo demo = new MemoryDemo();

        // 调用方法,产生新的栈帧在Java栈上
        demo.method1(localVar);
    }

    public void method1(int param) {
        // param 是方法的局部变量,存储在Java栈上
        // 下面是另一个局部变量,也存储在Java栈上
        double localMethodVar = 2.5;

        // 动态创建字符串,这将在堆上创建新对象
        String dynamicStr = new String("Dynamic String");

        // 调用另一个方法,这将在Java栈上创建另一个栈帧
        method2(dynamicStr);
    }

    public void method2(String str) {
        // str 是从method1传递过来的,存储在Java栈上
        System.out.println(str);
    }
}

我们将会按部分深入解读这段代码:

  1. 方法区

    • staticVar 是一个静态变量,它存储在方法区。所有的MemoryDemo实例都共享这个静态变量。
    • instanceVar 是一个实例变量,当一个新的MemoryDemo对象实例化时,它将被存储在堆上的那个对象内。
public static void main(String[] args) {
    // 局部变量,存储在Java栈上
    int localVar = 10;

    // 创建对象,存储在堆上
    MemoryDemo demo = new MemoryDemo();

    // 调用方法,产生新的栈帧在Java栈上
    demo.method1(localVar);
}
  1. Java栈

    • main方法开始执行,一个新的栈帧被推入Java栈。
    • localVardemomain方法的局部变量,它们被存储在这个栈帧中。
    • 使用new MemoryDemo() 创建一个新的MemoryDemo对象,该对象将被存储在堆上。
    • instanceVar 是这个新创建的对象的一部分,并在堆上的这个对象中存储。
public void method1(int param) {
    // param 是方法的局部变量,存储在Java栈上
    // 下面是另一个局部变量,也存储在Java栈上
    double localMethodVar = 2.5;

    // 动态创建字符串,这将在堆上创建新对象
    String dynamicStr = new String("Dynamic String");

    // 调用另一个方法,这将在Java栈上创建另一个栈帧
    method2(dynamicStr);
}
  1. Java栈

    • method1被调用时,另一个新的栈帧被推入Java栈顶部。
    • paramlocalMethodVar 是此方法的局部变量,并存储在新的栈帧中。
    • 使用new String("Dynamic String") 动态创建一个新的字符串对象,这个对象存储在堆上。
public void method2(String str) {
    // str 是从method1传递过来的,存储在Java栈上
    System.out.println(str);
}
  1. Java栈
    • method2被调用时,再次推入一个新的栈帧至Java栈。
    • str 是这个方法的局部变量,它存储在这个新的栈帧中。此变量引用了在堆上创建的dynamicStr对象。

这就是Java如何在其内存模型中管理变量和对象的方式。每个方法调用产生一个新的栈帧,每个新对象都存储在堆上,并且每个类的静态变量都存储在方法区。




用另简单的例子来详细说明Java内存中的这些区域如何工作。

假设我们有以下Java代码:

public class Student {
    private static String schoolName = "ABC High School";
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        String localVariable = "This is a local variable";
        Student studentA = new Student("John", 15);
        calculateAge(2023, studentA.age);
    }

    public static int calculateAge(int currentYear, int birthYear) {
        int age = currentYear - birthYear;
        return age;
    }
}
  1. 方法区

    • 存储Student类的元数据,如类名、父类名(Object),方法(maincalculateAge)和变量信息。
    • schoolName这个静态变量也会存储在方法区。
    • 当在main方法中通过new Student("John", 15)创建一个新的Student对象时,该对象实例(及其实例变量nameage)被存储在堆中的Eden区。
    • 如果这个对象后续经历了几次垃圾回收但仍然存活,它可能会被移动到Survivor区域,然后到老年代。
  2. Java栈

    • main方法被调用时,一个新的栈帧被推到Java栈顶,里面包含了argslocalVariablestudentA这些局部变量。
    • calculateAge方法被调用时,又会有一个新的栈帧被推到Java栈的顶部,这个栈帧包含了currentYearbirthYearage这些局部变量。
    • calculateAge方法返回后,其对应的栈帧从Java栈中弹出。当main方法返回后,其对应的栈帧也从Java栈中弹出。
  3. 程序计数器

    • 当执行到某一行Java字节码时,程序计数器会存储这行字节码的地址。

为了更直观地理解,想象Java内存为一个仓库,其中不同的区域有各自的作用。当你执行一个Java程序时,这些区域都在持续地进行分配和回收工作,以确保程序的高效运行。

你可能感兴趣的:(java,jvm,开发语言)