java 继承关系的内存图解 (通俗易懂)

简介: Java 继承关系中的内存图解,逐步拆分!

//头一回用新版编辑器,找不到目录按钮在哪儿了😂,大家可以看侧边栏将就一下。

前言:

本篇博文将给大家逐步拆分,细致地讲解一下在使用继承关系时,内存中究竟发生了什么。如果对jvm内存毫无了解或了解不多,大家可以先去看一下up之前写过的java创建对象的内存图解,有一定基础的读者就可以直接开始了。🆗,废话少说,Let's go!

一、代码准备 :

我们以Parent类为父类(采用标准JavaBean格式敲),采取多层继承的方式(便于大家加深理解)——首先定义Child类并让Child类去继承Parent类;然后再定义Hua类并让Hua类去继承Child类。最后以Test类为测试类继承关系图如下:

image.png

Parent类代码如下 :

packageknowledge.succeed.essence;
/*** 父类:Parent类(JavaBean标准)* */publicclassParent {
//成员变量privateStringname;
privateintage;
//无参构造publicParent() {
    }
//有参构造publicParent(Stringname, intage) {
this.name=name;
this.age=age;
    }
//getter,setter方法publicStringgetName() {
returnname;
    }
publicvoidsetName(Stringname) {
this.name=name;
    }
publicintgetAge() {
returnage;
    }
publicvoidsetAge(intage) {
this.age=age;
    }
}

Child类代码如下 :

packageknowledge.succeed.essence;
/*** Child:子类(派生类)*/publicclassChildextendsParent {    
//Child类构造器publicChild () {
    }
publicChild (Stringname, intage) {
super(name, age);
    }
//Child类成员方法publicvoidears() {
System.out.println("动耳神功!");
    }
}

Hua类代码如下 :

packageknowledge.succeed.essence;
/** 子类的子类 : Hua类 */publicclassHuaextendsChild{
//Hua类的构造publicHua() {
    }
publicHua(Stringname, intage) {
super(name, age);
    }
//Hua类特有成员方法publicvoidskill() {
System.out.println("五行相生,分石化玉。");
    }
}

Test类代码如下 :

packageknowledge.succeed.essence;
//测试类publicclassTest {
publicstaticvoidmain(String[] args) {
HuafuHua=newHua();      //从这行代码开始执行。//利用setter,getter方法来修改和获取fuHua对象的属性// (本质上修改的就是Parent父类的成员变量)fuHua.setName("云墨丹心");
fuHua.setAge(5000);
System.out.println("fuHua's name = "+fuHua.getName());
System.out.println("fuHua's age = "+fuHua.getAge());
System.out.println("----------------------------------");
//调用Child类的成员方法fuHua.ears();
//调用Hua类特有的成员方法fuHua.skill();
    }
}

运行结果 :

image.png

二、内存图解 :

(u‿ฺu)好滴,我们就以上面的代码为栗,给大家分析一下,在使用继承的整个过程中,内存中究竟发生了神马:

①包含main方法的类优先加载到方法区 :

因为main() 函数是程序的唯一入口,因此,包含main函数的Test类的字节码文件优先加载到方法区如下图所示 :

image.png

②main函数进栈 :

main方法加载进栈 ,如下图所示 :

image.png

③开始执行main方法中的代码 :

从main方法第一行开始执行代码。第一条语句是创建Hua类对象,从右向左执行。遇到了new关键字,new的是Hua类对象。

因为Hua类的字节码文件此时还没有加载到方法区,jvm无法识别Hua,因此下一步需要将Hua类的字节码文件加载到方法区(假设Hua类成员方法在方法区中的地址值为0x0088。但是,加载前jvm发现Hua类继承了Child类,而Child类又继承了Parent类,因此类的字节码文件的加载顺序为 :先加载Parent类,再加载Child类,最后加载Hua类(假设Child类成员方法在方法区中的地址值为0x0077,Parent类成员方法在方法区中的地址值为0x0066)。如下图所示 :

image.png

④开始执行new关键字操作 :

Hua类的字节码文件加载到方法区之后,new关键字会在堆内存开辟空间给新的Hua类对象,假设Hua类对象在堆内存中的地址值为0x0011如下图所示 :

image.png

⑤优先初始化高级别父类的内容 :

根据继承中对象的初始化顺序 : 先初始化父类内容,再初始化子类内容。fuHua对象属于Hua类,而Hua类继承了Child了,Child类又继承了Parent类。这里再强调一点,尽管Parent类中的属性为本类私有,但是Parent类提供的公开的setter和getter方法使得我们可以间接访问Parent类的属性。并且,不管是实例化Child类还是Hua类,最终使用的属性——本质上都是Parent类中的属性

因此,首先我们要将fuHua对象的堆空间划分出一块来,用于存放Parent类的内容,并根据Parent类的字节码文件,将划分出的空间在分成三部分。当然,其中的成员方法部分实际保存的是成员方法在方法区中的地址值,将来如果通过对象调用成员方法,可以通过这个保存的地址值进一步找到方法区中的成员方法如下图所示 :

image.png

继而,要对Parent类的属性进行初始化,在创建对象的内存图解一文中我们已讲过,对属性的初始化共分为三个步骤 : ①默认初始化,②显式初始化,③构造器初始化

注意 : name和age属性分别是String类型和int类型,因此默认初始化后的值分别为null和0。而在Parent类中,我们也没有对name和age进行人为的赋值,因此显式初始化步骤省去。又因为我们创建fuHua这一对象时,调用的是Hua类的无参构造,而Hua类的无参构造默认隐含的super语句会调用Child类的无参构造,Child类的无参构造默认隐含的super语句会调用Parent类的无参构造,所以最终会使用Parent类的无参构造来进行构造器初始化,因此构造器初始化后,name和age属性的值仍然为默认值。如下图所示 :

image.png

⑥接着初始化低级别父类的内容 :

🆗,Parent类内容初始化完成。下一步,要在fuHua对象的堆空间内再划分出一片区域来,去存放Child类的内容。如下图所示 :

image.png

⑦最后初始化子类内容 :

Hua类的父类的父类和Hua类的父类都已经初始化完成,下一步就是初始化子类——Hua类的内容了。如下图所示 :

image.png

⑧new关键字结束 :

创建的子类对象已初始化完成,最后,把对象的地址值返回给fuHua引用就可以了,如下图所示 :

image.png

至此,new关键字的操作完成。当我们通过"对象." 的形式来调用时,实质就是通过引用fuHua指向的堆内存中对象的地址,进一步找到对象的成员变量或者成员方法

⑨成员方法的使用细节1 :

继续执行main中的代码,下一步调用了setName方法,JVM会先通过fuHua这个引用找到堆内存中的对象,然后,再通过对象中Parent类内容的成员方法的地址,进而找到方法区中的成员方法,进行调用调用setName方法时,setName方法要进栈,如下图所示 :

image.png

⑩成员方法的使用细节2 :

调用setName方法,方法体中this.name = name;会将传入的形参赋值给Parent类的name成员变量,此处为"云墨丹心"。注意,赋值后,在堆内存中的name属性,实质保存的是"云墨丹心"在常量池中的地址值,真正的"云墨丹心"在常量池内并且有自己的地址值。如下图所示 :

image.png

⑩①成员方法的使用细节3 :

继续向下执行代码,setAge方法的调用与setName原理相同。只不过age属性的值会在堆内存中直接变化。如下图所示 :

image.png

继续向下执行代码,两条输出语句中分别调用了getName方法和getAge方法,调用原理仍然不变,同样,这两个方法也要依次进栈。调用getter方法,返回方法对应的属性,因为内存图实在放不下了,所以我这里就简单写了😂。如下图所示 :

image.png

⑩②子类特有成员方法的调用 :

接下来有调用Child类的特有方法ears(),以及Hua类的特有方法skill()。其实还是一个套路,先通过对象中保存的成员方法的地址值,找到方法区中对应的成员方法,继而成员方法进栈,实现调用。如下图所示 : (其中,黑色箭头代表Child类的ears方法蓝色箭头代表Hua类的skill方法

image.png

🆗,到这里main函数中的代码执行完毕。大家一定要记住,创建的fuHua对象,本质上是对分配的堆空间的引用(即指向这里的指针)真正的对象就是堆空间中分配的这块内容。并且,在继承关系中,不管是本类内容,还是父类内容,都在堆空间的本类对象中。 但平时工作学习中,我们将堆空间的引用简称为了什么什么对象,但它的本质一定要清楚。

三、小结 :

以上就是Java中使用继承关系的内存图解,应该是够详细了,至少图文并茂(bushi),如果发现有问题,欢迎指正,大家一起交流。感谢阅读!。

System.out.println("END---------------------------------------------------------------------------");

目录
相关文章
|
6天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
17天前
|
缓存 easyexcel Java
Java EasyExcel 导出报内存溢出如何解决
大家好,我是V哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。以下是V哥整理的解决该问题的一些常见方法,包括分批写入、设置合适的JVM内存、减少数据对象的复杂性、关闭自动列宽设置、使用Stream导出以及选择合适的数据导出工具。此外,还介绍了使用Apache POI的SXSSFWorkbook实现百万级别数据量的导出案例,帮助大家更好地应对大数据导出的挑战。欢迎一起讨论!
124 1
|
29天前
|
缓存 算法 Java
Java中的内存管理:理解与优化
【10月更文挑战第6天】 在Java编程中,内存管理是一个至关重要的主题。本文将深入探讨Java内存模型及其垃圾回收机制,并分享一些优化内存使用的策略和最佳实践。通过掌握这些知识,您可以提高Java应用的性能和稳定性。
44 4
|
29天前
|
存储 监控 算法
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,着重介绍垃圾回收(Garbage Collection, GC)机制。通过阐述GC的工作原理、常见算法及其在Java中的应用,帮助读者提高程序的性能和稳定性。我们将从基本原理出发,逐步深入到调优实践,为开发者提供一套系统的理解和优化Java应用中内存管理的方法。
|
1天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
14 6
|
5天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
20 2
|
6天前
|
存储 安全 Java
什么是 Java 的内存模型?
Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范的一部分,它定义了一套规则,用于指导Java程序中变量的访问和内存交互方式。
21 1
|
12天前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
24 1
|
1月前
|
Java
java操作内存,简单讲解varhandle的使用
本文介绍了Java中VarHandle的使用,它是一种从JDK 9开始引入的用于高效访问对象字段的特性。文章通过示例代码展示了如何通过VarHandle操作对象的字段,包括设置和获取字段值,以及如何通过MethodHandles.lookup().findVarHandle()方法获取VarHandle实例。VarHandle提供了一种比反射更高效的内存操作方式,并且支持原子操作。
33 0
java操作内存,简单讲解varhandle的使用
|
15天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。