• 7、Java 对象在 JVM 中的内存布局(详细说明)


    一、new 对象的几种说法

    初学 Java 面向对象的时候,实例化对象的说法有很多种,我老是被这些说法给弄晕。

    public class Test {
        public static void main(String[] args) {
            // 创建一个 ProgramLanguage 对象, 对象名是 java
            ProgramLanguage java = new ProgramLanguage();
            // 实例化一个 ProgramLanguage 对象, 对象名是 c
            ProgramLanguage c = new ProgramLanguage();
            // 把 ProgramLanguage 类实例化, 实例化后的对象的对象名是 python
            ProgramLanguage python = new ProgramLanguage();
        }
    }
    
    class ProgramLanguage {
        private Integer id;
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    下面的三种说法的操作都是实例化对象,只是说法不一样而已
    ① 🎄 创建一个 xxx 对象
    ② 🎄 实例化一个 xxx 对象
    ③ 🎄 把 xxx 类实例化

    二、Java 对象在内存中的存在形式

    这里先简单看一看 Java 对象在内存中的存在形式和几个内存相关的概念,后面还会详细介绍的。先看下面的几个知识点:

    1. 栈帧(Frame)

    💦 ① 方法被调用则栈帧创建,方法执行结束则栈帧销毁
    💦 ② 栈帧中存储了方法的局部变量信息
    💦 ③ 栈帧是分配给方法的一段栈空间

    在这里插入图片描述

    ⭐️ main 方法作为程序的入口肯定是第一个被调用的方法,所以会先创建 main 方法的栈帧
    ⭐️ 在 main 方法中调用了 test1 方法,并传入【55】作为参数给 test1 方法的局部变量 v,所以第二个创建的是 test1 方法的栈帧
    ⭐️ test1 方法中的代码很快就执行完了,所以 test1 的栈帧很快会被销毁(方法执行结束后该方法的栈帧销毁)
    ⭐️ 在 main 方法中调用了 test2 方法,并传入【88】作为参数给 test2 方法的局部变量 v,所以第三个创建的是 test2 方法的栈帧
    ⭐️ 在 test2 方法中调用了 test3 方法,并传入【666】作为参数给 test3 方法的局部变量 v,所以第四个创建的是 test3 方法的栈帧
    ⭐️ 当 test3 方法执行完毕后,test3 方法的栈帧被销毁
    ⭐️ test3 方法的结束也正是 test2 方法的结束,所以 test2 方法的栈帧也被销毁
    ⭐️ test2 方法的结束表示 main 方法的结束,所以 main 方法的栈帧会被销毁

    2. 对象在内存中的存在形式 ①

    💦 Java 中的所有对象都是通过 new 关键字创建出来的(new 关键字:实例化一个对象;向空间申请一段内存,用于存放刚刚实例化的对象)
    💦 所有的对象都存储在空间
    💦 所有保存对象的变量都是引用类型
    💦 局部变量是放在空间
    💦 Java 运行时环境中有个垃圾回收器(garbage collector),会自动回收没有被使用的(堆空间的)内存
    💦 当一个对象没有被任何引用指向的时候,会被 GC 回收掉内存

    分析下面的代码的内存布局:

    public class DogDemo {
        public static void main(String[] args) {
            Dog doggy = new Dog();
            doggy.age = 6;
            doggy.weight = 3.14;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    🍀 main 方法被调用,会在栈空间创建 main 方法的栈帧,main 方法的栈帧中会存放 main 方法中的局部变量信息(包括 args 和 main 方法中对象的引用 doggy
    🍀 在 main 方法中,通过 new 关键字实例化了 Dog 对象,Dog 对象存储在空间
    🍀 堆空间中有一段内存用于存储类的对象的数据,这段内存中存放了 Dog 对象的属性信息(如 age、weight)
    🍀 栈空间中的 doggy 变量代表堆空间中的对象的地址(通过地址可以访问对象)

    分析下面的代码的内存布局(稍微复杂)

    public class Dog {
        public int price;
    }
    
    • 1
    • 2
    • 3
    public class Person {
        public int age;
        public Dog dog;
    }
    
    • 1
    • 2
    • 3
    • 4
    public class Test {
        public static void main(String[] args) {
            Dog doggy = new Dog();
            doggy.price = 255;
    
            Person yeTing = new Person();
            yeTing.age = 20;
            yeTing.dog = doggy;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    🍀 main 方法被调用,会在栈空间创建 main 方法的栈帧,main 方法的栈帧中会存放 main 方法中的局部变量信息(包括 args、main 方法中对象的引用 doggy、对象的引用 yeTing)
    🍀 在 main 方法中,通过 new 关键字实例化了 Dog 对象,Dog 对象存储在空间。堆空间中有一段内存用于存储 Dog 对象的属性信息(如 price = 255)
    🍀 在 main 方法中,通过 new 关键字实例化了 Person 对象,Person 对象存储在空间。堆空间中有一段内存用于存储 Person 对象的属性信息(如 age = 20),堆空间中,Person 对象的属性 dog 是 Dog 对象的引用,所以它指向的是堆空间中的 Dog 对象(dog 指向的是栈空间中的 doggy 引用的堆空间的 Dog 对象。doggy 和 yeTing 指向的 Person 对象中的 dog 属性指向的是同一个对象)
    🍀 引用变量不一定是在栈空间(也可能是在堆空间,如上图中 yeTing 指向的 Person 对象中 dog,这个 dog 就是引用变量。但是,它是在堆空间。)
    🍀 引用变量指向对象实际上保存的是对象在堆空间中的地址值(如:doggy 保存的是 Dog 对象在堆空间的地址值、yeTing 保存的是 Person 对象在堆空间的地址值)

    3. 对象中的方法存储在那儿?

    看下面的代码,思考对象中的方法存储在那儿?

    public class Dog {
        public int price;
    
        public void run() {
            System.out.println(price + "_" + "run()");
        }
    
        public void eat() {
            System.out.println(price + "_" + "eat()");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    public class Test {
        public static void main(String[] args) {
            Dog dog1 = new Dog();
            dog1.price = 255;
            dog1.run();
            dog1.eat();
    
            Dog dog2 = new Dog();
            dog2.price = 552;
            dog2.run();
            dog2.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Java 虚拟机执行 Java 程序时会把内存划分为若干个不同的数据区域,主要包括:

    ① 🌾 PC 寄存器(Program Counter Register):存储 Java 虚拟机正在执行的字节码指令的地址
    ② 🌾 Java 虚拟机栈(Java Virtual Machine Stack):存储 Java 方法的栈帧(① 方法被调用的时候会在栈空间创建该方法的栈帧,该方法执行完毕后,该方法对应的栈帧会销毁;② 栈帧中会存放方法中的局部变量信息)【栈空间】
    ③ 🌾 堆空间(Heap):存储被 GC(垃圾回收器) 所管理的各种对象(GC 管理的是通过 new 关键字创建的对象)
    ④ 🌾 方法区(Method Area):存储每个类的结构信息(如:字段和方法信息、构造方法和普通方法的字节码信息)
    ⑤ 🌾 本地方法栈(Native Method Stack):用来支持 native 方法的调用(如:用 C 语言编写的方法)


    4. Java 对象在内存中的存在形式 ②

    String:
    🌷 是字符串,在 Java 编程中被频繁地使用,但它是引用类型
    🌷 Java 中双引号包裹的内容默认就是字符串类型
    🌷 Java 中被双引号包裹的内容叫做字符串常量
    🌷 字符串常量存储在字符串常量池中(String Constant Pool)
    🌷 jdk1.7 之前,字符串常量池在方法区;后来被移动到了堆空间。所以,jdk1.8的字符串常量存储在堆空间的字符串常量池中
    后面学习 String 的时候还会细说

    分析下面代码的内存布局:

    public class Dog {
        String name;
        int age;
        String color;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class DogDemo {
        public static void main(String[] args) {
            Dog doggy = new Dog();
            doggy.name = "笑天";
            doggy.age = 6;
            doggy.color = "黑";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    三、类中属性详细说明

    上一篇文章【通过 Java 官方教程学习一下 Java 的类和对象】中说到:
    🌻 现实世界中的对象有状态(State)和行为(Behavior),面向编程中的对象有属性(Field)和方法(Method)。
    🌻 类是创建单个对象的蓝图(模板)
    在这里插入图片描述
    下面详细说明一下类中【属性】这个概念。其实上篇文章已经能够很好理解,这里只是再补充一下而已。


    🌱 属性、成员变量、字段(field)指的是同一个东西(即一个类的状态)习惯上把现实世界的对象的状态(State)和编程中的属性联系在一起,便于理解
    🌱 属性可以是基本数据类型或引用类型(自定义类,接口,数组 …)
    🌱 定义属性的语法:访问修饰符 + 属性类型(eg: String、int、Dog、Bicycle) + 属性名
    🌱 访问修饰符(控制属性被访问的范围)有四种:public、protected、默认(不写)、private【后面会详细说】

    /**
     * 访问修饰符有四种:public、protected、默认(不写)、private
     */
    public class Dog {
        public String name;
        protected int age;
        String color;
        private double weight;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    🌱 如果不给对象的属性赋值,属性会有初始值

    /**
     * 测试若不给对象的属性赋初始值, 它们的默认初始值
     */
    public class FiledInitialValueTest {
        private int score;
        private short age;
        private byte month;
        private long salary;
        private float height;
        private double pi;
        private char firstName;
        private boolean isTrueLove;
        private Person person;
    
        public static void main(String[] args) {
            FiledInitialValueTest test = new FiledInitialValueTest();
            System.out.println("\n若不给对象的属性赋值, 初始值如下所示:");
            System.out.println("score【int】 = " + test.score);
            System.out.println("age【short】 = " + test.age);
            System.out.println("month【byte】 = " + test.month);
            System.out.println("salary【long】 = " + test.salary);
            System.out.println("height【float】 = " + test.height);
            System.out.println("pi【double】 = " + test.pi);
            // 字符类型的属性的初始值是空串(在控制台无法看到)
            System.out.println("firstName【char】 = " + test.firstName);
            // 字符类型的属性的初始值强制类型转换为 int 后是【0】
            System.out.println("firstName【(int)char】 = " + (int) test.firstName);
            System.out.println("isTrueLove【boolean】 = " + test.isTrueLove);
            System.out.println("person【person】 = " + test.person);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    四、细小知识点

    1. 如何创建对象

    必须先有类(模板)才能创建对象

    通过【new】关键字创建类的对象。【new】:向堆空间申请一块内存存储对象数据

    public class TestCreateObject {
        public static void main(String[] args) {
            // (1) 先声明再创建
            Dog dog; // 声明 
            dog = new Dog(); // 通过 new 关键字创建对象
    
            // (2) 声明并创建对象 
            Dog doggy = new Dog();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 如何访问属性

    可通过【.】号访问属性或调用方法

    可把 . 看做【的】、【の】

    在这里插入图片描述

    五、Exercise

    看代码,画图:

    public class Person {
        private int age;
        private String name;
    
        public static void main(String[] args) {
            Person yangJiaLi = new Person();
            yangJiaLi.age = 17;
            yangJiaLi.name = "杨嘉立";
    
            // 下面的一行代码有2种说法:
            // 1. 把 yangJiaLi 赋值给 yjl
            // 2. yjl 指向 yangJiaLi
            Person yjl = yangJiaLi;
            System.out.println(yjl.age); // 17
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    六、总结

    本篇文章的重点是第二节【Java 对象在内存中的存在形式】
    需重点知道:
    在这里插入图片描述

    我才疏学浅,但又颇爱分享。我不遗余力把每个知识点弄清楚,为了让文章丰富多彩,花费大量时间画图,只为您的一个【点赞】。若您在文章中发现错误,请不吝赐教。

    ✏️ 感谢关注,感谢点赞,感谢评论!
    ✏️ ありがとう 关注,ありがとう 点赞,ありがとう 评论!
    ✏️ Thank you 关注,Thank you 点赞,Thank you 评论!
    ✏️ 我一定会加油写好每一篇文章的!
    ✏️ I will try my utmost to write every article!
    ✏️ I must 頑張って write 好每一篇文章的!

    頑張って!頑張って!頑張って!
    在这里插入图片描述

  • 相关阅读:
    el-table如何实现自动缩放,提示隐藏内容
    elasticsearch-reinde 实际操作步骤
    笔记 | 嵌入式系统概论
    NoSql的优势在哪里,NoSql是什么
    为什么需要协调能力?如何提高协调能力?
    Linux—vmstat命令详解
    Acwing1015. 摘花生
    DML(插入 更新 删除),附代码理解
    Spring5.3学习——from 官网 day1-1
    网络安全保险行业面临的挑战与变革
  • 原文地址:https://blog.csdn.net/m0_54189068/article/details/126620737