之前在[ 图形化理解Java中的形参和实参]一文中我们知道了如下两个知识点:

    • JVM的内存空间主要划分为栈(stack)、堆(heap)、方法区(method area)、本地方法栈(native method stack)和寄存器(register),通常在分析内存使用时主要关注于前三部分 image-20200417101436408.png
    • Java中的参数传递根据形参是八大基本数据类型还是引用类型可以分为值传递和引用传递,不同的传递方式对于内存空间的使用是不同

    而在面向对象编程中,我们需要从解决的问题中抽象出类,然后在调用类的属性和方法时需先实例化类对象,那么在类对象的实例化过程和对于属性和方法的使用过程中,JVM的内存空间是如何变化的呢?

    假设现在有Student类,它只有一个成员变量name和一个成员方法showInfo():

    1. class Student{
    2. String name;
    3. public void showInfo(){
    4. System.out.println("Name is: " + name );
    5. }
    6. }

    那么通过实例化类对象,我们就可以使用Student类:

    1. public class thisTest {
    2. public static void main(String[] args) {
    3. Student s = new Student();
    4. s.name = "Kobe";
    5. s.showInfo();
    6. }
    7. }

    那么在使用实例化列对象和使用对象的过程中,JVM的内存空间是如何变化的呢?下面我们通过一张图按步骤的理解一下:
    类内存图.png

    如上所述,我们重点只关注栈内存、堆内存和方法区内存三个部分

    • 首先将Student和thisTest的class数据保存到方法区中,其他每个.class包含各自的成员变量和成员方法,其中成员方法所在内存地址假设为0x666;
    • 将mian(String[] args)入栈,实例化Student对象并保存在堆中,初始化成员变量和成员方法并赋予默认值。name的默认值是null,而成员方法这里保存的是它在方法区中的地址,所以默认值为0x666。同时假设对象在堆中的地址为0x2333。接着创建变量s保存在栈中,它保存的是Student在堆中的地址,即0x233;
    • 在实例化完Student对象后,接着为name变量赋值。首先找到栈中的s,s保存了对象在堆中的地址,依靠地址找到对象中的name变量,然后修改为Kobe;
    • 接着调用showInfo()方法,同样通过s保存的地址找到堆中的对象的成员方法,因为成员方法保存的是它在方法区中的地址,因此通过保存的方法区地址找到成员方法showInfo(),将其压入栈中进行调用,输出“Name is Kobe”;
    • 当调用完成员方法后就不再使用,因此接着将其出栈。由于栈中的main()也没有其他动作执行,接着也将其出栈,最后JVM在判断两个对象都不可达时,回收垃圾,结束。

    明白了实例化一个对象的内存使用过程,那么多个对象的使用类似。