Java 底层思考系列:从 JVM 栈帧开始,我真正理解了Java 为什么设计抽象类
从 JVM 栈帧开始我真正理解了Java 为什么设计抽象类关键词JVM、Stack Frame、动态链接、抽象类、多态、方法调用、Java底层原理最近背 Java 八股时看到了 JVM 的栈帧Stack Frame。起初觉得这只是 JVM 的一个知识点局部变量表操作数栈动态链接返回地址后来越想越觉得这些东西竟然和 Java 的面向对象设计全部串起来了。尤其是为什么 Java 要设计抽象类以前我的回答一直都是“为了规范子类。”今天终于理解真正的原因要从 JVM 如何调用一个方法开始。一、一个方法调用到底发生了什么例如publicstaticvoidmain(String[]args){AnimalanimalnewDog();animal.eat();}以前我脑子里的理解只有调用 eat() ↓ 输出结果现在真正发生的是Java代码 ↓ 编译成 .class ↓ JVM加载类 ↓ 调用方法 ↓ 创建栈帧Stack Frame ↓ 执行字节码 ↓ 方法结束 ↓ 栈帧销毁也就是说每调用一个方法JVM 都会创建一个新的栈帧。方法结束以后栈帧立即弹出。二、栈帧里面到底有什么一个栈帧主要包含四部分。Stack Frame ├── 局部变量表Local Variable Table ├── 操作数栈Operand Stack ├── 动态链接Dynamic Linking └── 返回地址Return Address① 局部变量表负责保存局部变量。例如publicstaticintadd(inta,intb){intcab;returnc;}那么局部变量表 a b c全部保存在这里。② 操作数栈负责计算。例如intcab;JVM 实际会执行push a push b ↓ ↓ 结果压回栈所以所有 - * / %都依赖操作数栈。③ 动态链接Dynamic Linking这里看着唬人其实就一句话秒了帮助 JVM 找到真正要执行的方法。什么意思例如AnimalanimalnewDog();animal.eat();变量类型Animal对象类型DogJVM 怎么知道应该执行Animal.eat()还是Dog.eat()答案就是动态链接。运行期间JVM 根据对象真正的类型找到Dog.eat()然后执行。这一瞬间我突然意识到这就是多态工作的底层基础。④ 返回地址方法执行结束以后CPU 怎么知道回哪里例如main(){add();System.out.println(结束);}add()执行完成以后并不是程序结束。而是继续执行System.out.println(结束);所以栈帧里面还保存着返回地址。三、动态链接让我重新理解了多态以前觉得多态只是AnimalanimalnewDog();今天终于知道真正发生的是animal.eat() ↓ JVM ↓ 动态链接 ↓ Dog.eat()这也是为什么Java 可以父类引用 ↓ 子类对象 ↓ 调用子类方法四、然后我突然想到一个问题既然JVM 会找到真正的方法。那为什么 Java 还需要抽象类例如classShape{publicdoublearea(){return0;}}这个真的合理吗圆形怎么算面积矩形怎么算面积三角形怎么算面积父类其实根本不知道。如果写一个return0;只是为了让代码能够编译。那这个方法其实毫无意义。五、这就是抽象类存在的意义于是Java 提供了abstractclassShape{publicabstractdoublearea();}这里父类直接告诉所有子类我不知道怎么实现。但是你必须实现。于是classCircleextendsShape{Overridepublicdoublearea(){returnMath.PI*r*r;}}如果子类没有实现编译直接失败。这就是抽象类真正解决的问题。它不是帮助 JVM。而是帮助程序员。六、我曾经有一个错误理解刚开始学习的时候我甚至认为抽象类是不是为了节省 JVM 内存后来查阅 JVM 规范以后发现这是错误的。原因很简单。抽象方法依然存在。例如abstractvoideat();JVM 依然知道方法名 参数 返回值 访问权限只是没有方法体Code Attribute。所以抽象类几乎不是为了节省内存。真正的价值是表达设计意图。它告诉所有开发者这个行为一定存在。 但是 父类不知道如何实现。 必须交给子类。这是面向对象设计。而不是JVM 内存优化。七、整个知识终于串起来了以前这些知识在我脑子里都是独立的。JVM 抽象类 继承 多态 Override今天终于变成了一张图。Java代码 ↓ 编译成字节码 ↓ JVM加载类 ↓ 方法调用 ↓ 创建栈帧 ↓ 动态链接 ↓ 找到真正的方法 ↓ 为什么需要真正的方法 ↓ 因为存在 Override ↓ 为什么允许 Override ↓ 因为存在继承 ↓ 为什么父类没有实现 ↓ 因为父类无法提供通用实现 ↓ 于是 抽象类诞生这一刻JVM 和 Java 面向对象设计终于连接到了一起。八、总结以前学习 Java总是在背什么是继承什么是多态什么是抽象类今天第一次站在 JVM 的角度去理解发现这些设计并不是孤立存在的。动态链接让 JVM 能够在运行时找到真正的方法。方法重写让不同子类拥有不同实现。抽象类则告诉编译器父类不知道怎么做所以必须由子类完成。原来Java 面向对象的设计并不是为了炫技。而是为了让程序的结构更加合理让错误尽可能提前暴露让扩展变得更加自然。真正的学习不是记住一个知识点而是把一个知识点和整个知识体系连接起来。如果一门语言的每一个设计都能回答 “为什么”那你离真正理解它就更近了一步。