前言
这两天在重温java继承,做了几个实例,特别是在做多态实例时对java底层产生了猜想,有时候会根据我的猜想出结果,有时候会有差错,赶紧查阅资料,浅析一下继承的底层.
方法区概念的引入
- 方法区,与java堆一样,是各个线程共享的内存区域.
- 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和java堆区一样都可以是不连续的.
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误.(方法区加载大量的第三方的jar包,tomcat部署过多,大量动态的生成反射类也会).
- 关闭JVM就会释放这个区域的内存
栈堆方法区
之前只知道java底层有java栈和java堆,思考底层运行的时候囿于此,所以有纰漏.
由于对于一个类class来说,其创建的实例是独一无二的,故其引用放在栈,自身放在堆中.而静态变量,方法以及Class对象则是堆所有类实例来说是共享的,单独处理,放在方法区中.
类加载
类加载过程包括:
加载:根据类名称获取二进制字节流,转化成方法区数据结构,并在堆中生成类class对象
验证:验证是否安全
准备:为静态变量在方法区分配内存
解析:把符号引用转化成直接引用
初始化:一般在new对象的时候会触发初始化,并为静态变量附上真实的值。
上面也说明了:类加载后会在方法区存放该类的信息,创建对象时,创建出一个对象存放到方法区,并在栈中有指向该对象的引用,堆中每个对象除了保存类的实例变量之外,还保存着实际类信息的引用,如下图所示:
我们先来看animal.animalShout();这句代码的执行过程是:
查看animal的对象类型,找到Cat类型,在Cat类型中找animalShout方法,发现没有,到父类中寻找
在父类Base中找到了方法animalShout,开始执行animalShout方法
在animalShout方法中发现调用了shout方法,就从Cat类型开始寻找shout方法
在Child类型中找到了shout()方法,执行Cat中的shout()方法,执行完后返回animalShout方法
继续执行action方法,输出打印信息
寻找要执行的实例方法的时候,是从对象的实际类型信息开始查找的,找不到的时候,再查找父类类型信息。
如果继承的层次比较深,那么如果调用的存在最底层的父类,则调用的效率是比较低的,所以系统使用一种称为虚方法表的方法来优化调动的效率。
所谓虚方法表,就是在类加载的时候,为每个类创建一个表,这个表包括该类的对象所有动态绑定的方法及其地址,包括父类的方法,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。