第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;
}
下面的三种说法的操作都是实例化对象,只是说法不一样而已
① :christmas_tree: 创建一个 xxx 对象
② :christmas_tree: 实例化一个 xxx 对象
③ :christmas_tree: 把 xxx 类实例化

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

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

1. 栈帧(Frame)

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

在这里插入图片描述

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

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

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

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

public class DogDemo {
    public static void main(String[] args) {
        Dog doggy = new Dog();
        doggy.age = 6;
        doggy.weight = 3.14;
    }
}

在这里插入图片描述

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

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

public class Dog {
    public int price;
}
public class Person {
    public int age;
    public Dog dog;
}
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;
    }
}

在这里插入图片描述

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

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

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

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

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

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

public class Dog {
    String name;
    int age;
    String color;
}
public class DogDemo {
    public static void main(String[] args) {
        Dog doggy = new Dog();
        doggy.name = "笑天";
        doggy.age = 6;
        doggy.color = "黑";
    }
}

在这里插入图片描述

三、类中属性详细说明

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


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

/**
 * 访问修饰符有四种:public、protected、默认(不写)、private
 */
public class Dog {
    public String name;
    protected int age;
    String color;
    private double weight;
}

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

/**
 * 测试若不给对象的属性赋初始值, 它们的默认初始值
 */
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. 如何创建对象

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

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

public class TestCreateObject {
    public static void main(String[] args) {
        // (1) 先声明再创建
        Dog dog; // 声明 
        dog = new Dog(); // 通过 new 关键字创建对象

        // (2) 声明并创建对象 
        Dog doggy = new Dog();
    }
}

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
    }
}

在这里插入图片描述

六、总结

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

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

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

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

相关文章
|
2月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
50 0
|
2月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
71 0
|
6天前
|
存储 Java
Java中判断一个对象是否是空内容
在 Java 中,不同类型的对象其“空内容”的定义和判断方式各异。对于基本数据类型的包装类,空指对象引用为 null;字符串的空包括 null、长度为 0 或仅含空白字符,可通过 length() 和 trim() 判断;集合类通过 isEmpty() 方法检查是否无元素;数组的空则指引用为 null 或长度为 0。
|
25天前
|
Java
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
|
24天前
|
安全 Java
Object取值转java对象
通过本文的介绍,我们了解了几种将 `Object`类型转换为Java对象的方法,包括强制类型转换、使用 `instanceof`检查类型和泛型方法等。此外,还探讨了在集合、反射和序列化等常见场景中的应用。掌握这些方法和技巧,有助于编写更健壮和类型安全的Java代码。
38 17
|
1月前
|
Java
java代码优化:判断内聚到实体对象中和构造上下文对象传递参数
通过两个常见的java后端实例场景探讨代码优化,代码不是优化出来的,而是设计出来的,我们永远不可能有专门的时间去做代码优化,优化和设计在平时
32 15
|
1月前
|
存储 监控 算法
Java JVM 面试题
Java JVM(虚拟机)相关基础面试题
|
2月前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
54 8
|
2月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
64 3

热门文章

最新文章