Java-从JVM的角度重新理解面向对象(OOP)

Java-从JVM的角度重新理解面向对象(OOP)

背景

在初次学习面向对象的时候,我时时为那些抽象的概念所迷茫,如今从Jvm虚拟机运行原理重新理解面向对象,属实打开了一扇新世界的大门,理解了Java面向对象的内存机制,一切面向对象的概念在我眼中犹如透明

什么是JVM

JVM虚拟机机制是Java跨平台兼容性的支撑,在任何安装了JVM虚拟机的平台,你的JAVA代码就能在任意平台无限切换,JVM虚拟机一般是在主机内存中专门开辟一块空间,在这个空间内处理Java代码

JVM的常见内存结构

在运行java程序时,JVM会将不同类型的数据放入不同类型的数据结构,本文涉及的仅有栈、堆

在jvm中,内存被分为堆与栈

  • 栈:容量小,存取速度快,适合存储生命周期短的数据,存储变量(局部变量)与方法,先进后出(想象成一个瓶子,最先扔进去的一定在栈底,它一定最后才能出来)
  • 堆:容量大,存取速度慢,适合存储生命周期长的数据,堆适合存储对象对象一般在参数中引用数据类型传递

从JVM角度看面向对象多场景

学会了JVM的内存机制,你再去看看继承、接口、多态、static、等等东西,它们在你眼里就是透明的了,我们不用再去琢磨那些乱七八糟的概念,眼里直接就有这些概念的背后逻辑,因为Java概念本身依赖就是JVM虚拟机机制

以下介绍了面向对象主要概念的JVM视角,看完你便能够举一反三,理解面向对象其他那些犄角旮旯的内容

程序运行

我们的Java程序未编译是一个.java文件,经过编译是一个.class文件,存储在硬盘中

这时如何我运行一个.class文件,且看内存如何执行

  1. 开辟一块内存空间,称JVM
  2. 将.class文件中的数据提取到JVM
  3. CPU调度运行

常量与变量判定

扫描二维码关注公众号,回复: 8900666 查看本文章

数据加载到内存中时,我们按着运行期数据的是否变化把数据分成常量和变量

常量有如:圆周率Π、重力系数(9.8)

变量有如:mp3文件,其在内存中运行是时刻变化

方法调用(值传递)的内存结构

示例代码

以下面这段代码为例,讲解jvm内存的运行机制

public class test01 {
    public static void main(String[] args) {
        int i  = 1 ;
        print(i);
    }

    public  static  void print(int i ){
        System.out.println(i);
    }
}


方法调用(值传递)内存结构图

  1. 在内存中,开辟一块JVM的空间
  2. 在JVM中,开辟栈空间数据共享区空间
  3. 将类与类中方法调入数据共享区
  4. 在栈中开辟一块内存空间,主方法入
  5. 主方法空间中,开辟一块空间,主方法中的数据(int i = 1 )入
  6. 主方法调用print方法,在主方法空间中开辟一块空间,print方法入
  7. print方法空间中,未其方法的参数开辟一块空间(几个参数几块空间),参数入
  8. 将主方法中的int空间值传给print方法中int空间

经过以上分析,很显然,主方法传入的实参与print方法的实参是两块不同的内存空间,这也从底层解释了值传递后,在方法中改变参数的值,并不会影响主方法中的参数值

引用数据类型的内存结构

数组

数组作为参数去传递是一个典型的引用数据类型传递的过程

我们说引用数据类型一般存储在堆中

示例代码
public class test01 {
    public static void main(String[] args) {
        int[] arr  = {1,2,3} ;
    }

且看主方法的执行在内存中如何体现

引用数据类型内存结构图

  1. JVM中的栈中开辟一块空间,主方法(main方法)入
  2. 主方法中的数组入堆,形成地址号
  3. 主方法空间内开辟一块空间,存储堆中数组的地址

以上解释了引用数据类型数据传递后,在方法中改变参数的值将直接影响主方法中的参数值

因为实参与形参在内存中都是一块空间

对象

传递对象示例代码

实体类

public class Dog {
    /*属性*/
    private  String name ; 
    private  float height ;
    /*构造器*/
    public Dog(String name, float height) {
        this.name = name;
        this.height = height;
    }
}

测试类

public class test01 {
    public static void main(String[] args) {
        /*创建两条狗*/
        Dog dog = new Dog("小白",5) ;
        Dog dog1 = new Dog("小黄",3) ;
    }
}
传递对象内存结构图

道理与数组一样,不赘述

匿名对象的内存结构

匿名对象是指没有被引用的对象,由于没有栈中的变量来引用,所以就会被回收掉,所以匿名对象是无意义的,实际开发尽量避免匿名对象

匿名对象代码示例

public class test01 {
    public static void main(String[] args) {
        /*创建两条狗*/
         new Dog("小白",5) ;
         new Dog("小黄",3) ;
    }
}

匿名对象内存结构图

有关This关键字的内存结构

this简介

在每一个对象的方法中都包含一个this关键字,代表调用当前方法对象自身(简记为对象的自身)(简记为方法被谁调用谁就是this

this的本质其实就是一块内存空间的地址

如下所示

public class test01 {
    public static void main(String[] args) {
        /*创建一条狗*/
        Dog dog = new Dog("小白",5) ;
        System.out.println(dog);
    }
}

打印的结果是Dog@1eec35

这个东东就是this,代表这条狗的地址

this的特点

  • this只能在类的对象方法中使用(所谓类的对象方法,可以记成不带static的方法
  • this可以在方法内区分同名的类的属性和参数名,有this的一定是属性,没有this的一定是方法的参数名。

如狗的实体类构造器

    public Dog(String name, float height) {
        this.name = name;
        this.height = height;
    }

this.name代表狗对象的属性

无this的name代表传递过来参数

this的内存结构图

示例代码

实体类代码

public class Dog {
    /*属性*/
    private  String name ;
    private  float height ;
    /*构造器*/
    public Dog(String name, float height) {
        this.name = name;
        this.height = height;
    }
    /*吃翔*/
    public void eatShit(){
        System.out.println(this.name+"在吃翔") ;
    }
}

测试类代码

public class test01 {
    public static void main(String[] args) {
        /*创建两条狗*/
        Dog dog = new Dog("小白",5) ;
        Dog dog1 = new Dog("小黄",3) ;
        
        dog.eatShit();
        dog1.eatShit();
    }
}
小白在吃翔
小黄在吃翔

且看其内存结构图如下

0x9999与0x9988就是this

这里还要说一下,所有方法(类方法、对象方法)都存放在资源共享区,方法中涉及的属性依据数据类型存放在堆或栈

类的继承的内存结构

示例代码

/**
 * 父类
 */
class Father{
    String name;
    int age;
    public void sleep(){
        System.out.println("爸爸在睡觉");
    }
}
/**
 * 子类
 */

class Son extends Father{

    public void playGameGame(){
        System.out.println(name+"正在玩耍"+"  年龄:"+age);
    }
}

/*子类*/
class Daughter extends Father{

    public void drinkMilk(){
        System.out.println(name+"正在喝奶"+"  年龄:"+age);
    }
}

class ExtendsDemo2{
    public static void main(String[] args){
        Son  son = new Son();
        son.name = "儿子";
        son.age = 3;
        son.playGameGame();

        Daughter dau = new Daughter();
        dau.name = "女儿";
        dau.age = 4;
        dau.drinkMilk();
    }

}
结果:
儿子正在玩耍  年龄:3
女儿正在喝奶  年龄:4

内存结构图

大概说一下要点,以女儿为例

  • 创建女儿对象时,发现女儿类继承自父亲类女儿对象入堆,并在女儿对象的空间内开辟一块空间,将其所继承的父亲类的name属性存入该空间

  • dau.name = "女儿";
    dau.age = 4;
    

设置女儿对象的name和age属性,首先在女儿对象的空间内查找该属性,查询不到,到父亲空间内查询,这时就查到了,将所查设置为所设

  • 在执行对象方法drinkMilk时,发现出现name属性,首先在女儿对象的空间内查找该属性,查询不到,到父亲空间内查询,这时就查到了
发布了161 篇原创文章 · 获赞 93 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/JunSIrhl/article/details/103666573