可以看到,由自定义的加载类只能获取同包下的class,而系统的class不能被加载,而且由Class.forName()获取的类与自定义加载类得到的类不是同一个类。
根据五种初始化的条件,父类也会被初始化,但是,上边的代码运行结果显示,父类和接口都没有被初始化,这又是怎么回事呢?
系统提供了三种类加载器,分别是:
启动类加载器(Bootstrap ClassLoader),该加载器会将\lib目录下能被虚拟机识别的类加载到内存中。
扩展类加载器(Extension ClassLoader),该加载器会将\lib\ext目录下的类库加载到内存。
应用程序类加载器(Application ClassLoader),该加载器负责加载用户路径上所指定的类库。
我们自定义的ClassLoader继承自应用程序类加载器,当自定义类加载器找不到所加在的类时,会使用启动类加载器进行加载,当启动类加载器加载不到时,由扩展类加载,扩展类加载不到时有应用程序类加载。这也是为什么上边的代码能够成功运行的原因。
三 字节码执行引擎 运行时栈帧结构栈帧是虚拟机栈的栈元素,栈帧存储了局部变量表,操作数栈,动态连接,返回地址等信息。每一个方法的调用都对应着一个栈帧在虚拟机栈中的入栈和出栈。
局部变量表由方法参数,方法内定义的局部变量组成,容量以变量槽(Slot)为最小单位。如果该方法不是static方法,则局部变量表的第一个索引为该对象的引用,用this可以取到。
操作数栈最开始为空,由字节码指令往栈中存数据和取数据,方法的返回值也会存到上一个方法的操作数栈中。
动态连接含有一个指向常量池中该栈帧所属方法的引用,持有该引用是为了进行动态分派。
方法返回地址存放的是调用该方法的pc计数器值,当方法正常返回时,就会把返回值传递到上层方法调用者。当方法中发生没有可被捕获的异常,也会返回,但是不会向上层传递返回值。
方法调用Java是一门面向对象的语言,它具有多态性。那么虚拟机又是如何知道运行时该调用哪一个方法?
静态分派是在编译期就决定了该调用哪一个方法而不是由虚拟机来确定,方法重载就是典型的静态分派。
动态分派是在虚拟机运行阶段才能决定调用哪一个方法,方法重写就是典型的动态分派。
动态分派的实现:当调用一个对象的方法时,会将该对象的引用压栈到操作数栈,然后字节码指令invokevirtual会去寻找该引用实际类型。如果在实际类型���找对应的方法,且访问权限足够,则直接返回该方法引用,否则会依照继承关系对父类进行查找。实际上,如果子类没有重写父类方法,则子类方法的引用会直接指向父类方法。
基于栈的字节码执行引擎不管是解释型语言还是编译型语言,机器都无法理解非二进制语言。高级语言转化成机器语言都遵循现代经典编译原理。即执行前对程序源码进行词法和语法分析,构建抽象语法树。C语言等编译型语言会由单独的执行引擎做这些工作,而Java语言等解释型语言语法抽象树由jvm完成。jvm可以选择通过解释器来解释字节码执行还是通过优化器生成机器代码来执行。
常用的两套指令集架构分别是基于栈的指令集和基于寄存器的指令集。