Java虚拟机把描述类的Class文件加载到内存,并对数据进行校验、转换解析和初始化,形成可以被虚拟机直接使用的Java类型,这个过程是虚拟机的类加载机制
类加载机制:
一个类型被加载到虚拟机的过程:加载、验证、准备、解析、初始化、使用和卸载。解析阶段是可以变化的。第一个阶段的记载由逊尼基的具体实现来自有把握,而初始化是有条件的:
-
遇到new、getstatic、pubstatic或invokestatic这四条指令时,若类型没有进行过初始化,会先进行初始化,能触发这四条指令的场景有:new实例化对象时 ,使用一个类型的静态字段(static final除外)或者静态方法时。
-
使用java.lang.reflect进行反射调用的时候。
-
初始化类时,其父类如果没初始化,会初始化弗雷
-
虚拟机启动时,虚拟机会先初始化主类
在编译阶段"hello world"已经存放在了常量池之中
类加载过程中子类在初始化时必须要求其父类已经完成了初始化,而一个接口在初始化时并不会要求其父接口已经完成了初始化。
类加载过程:
类的加载全过程为加载、验证、准备、解析和初始化。
加载:虚拟机需要完成的事情
1.通过类的全限定名来获取这个类的二进制字节流
2.根据字节流代表的静态存储结构转换为方法区的运行时数据结构
3.生成代表这个类的类对象,作为方法和变量的入口
非数组类型的加载阶段,既可以使用Java虚拟机里内置的引导类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员通过定义自己的类加载器去控制字节 流的获取方式(重写一个类加载器的findClass()或loadClass()方法),实现根据自己的想法来赋予应用 程序获取运行代码的动态性。
对数组类而言,数组类不ton过类加载器创建,它是由虚拟机直接在内存中动态沟造出来的,但是数组类的元素类型最终还是要靠类加载器来完成加载。
验证:
验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
1.文件格式验证(验证Class文件格式的规范)
·是否以魔数0xCAFEBABE开头。
·主、次版本号是否在当前Java虚拟机接受范围之内。
·常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
·指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
·CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据。
·Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
2.元数据验证
·这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
·这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
·如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
·类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方 法重载,例如方法参数都一致,但返回值类型却不同等)。
3.字节码验证(对类的方法体进行校验分析)
·保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似于“在操作 栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中”这样的情况。
·保证任何跳转指令都不会跳转到方法体以外的字节码指令上。
·保证方法体中的类型转换总是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全 的,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个 数据类型,则是危险和不合法的。
4.符号引用验证(对类自身意外常量池中的各种符号引用的各类信息进行校验)
·符号引用中通过字符串描述的全限定名是否能找到对应的类。
·在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。
·符号引用中的类、字段、方法的可访问性(private、protected、public、)是否可被当 前类访问。
准备:
正式为类中静态变量分配内存并设置初始值。只对类变量进行内存分配,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
解析:
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程
·符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何 形式的字面量,只要使用时能无歧义地定位到目标即可。符
直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能 间接定位到目标的句柄。
初始化:
类的初始化时类加载过程中的最后一步。在准备阶段变量就已经赋值过0了,在初始化阶段则会根据程序员通 过程序编码制定的主观计划去初始化类变量和其他资源。初始化阶段就是执行类构造器<clinit>方法的过程。
初始化阶段就是执行类构造器()方法的过程。它是javac编译器的自动生成物。Java虚拟机会保证在子类的<clinit>方法执行前,父类的()方法已经执行 完毕。因此在Java虚拟机中第一个被执行的()方法的类型肯定是java.lang.Object。<clinit>方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的 赋值操作,那么编译器可以不为这个类生成()方法。,执行接口的()方法不需要先执行父接口的<clinit>方法, 因为只有当父接口中定义的变量被使用时,父接口才会被初始化
类加载器
在加载阶段中通过一个类的全限定名来获取描述该类的二进制字节流这个动作放到虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动 作的代码被称为“类加载器”。
只有在同一个类加载器下才能比较两个类是否相等。这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance() 方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。
双亲委派模型:
站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有 的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。
启动类加载器:存放在 <JAVA_HOME>\lib目录,启动类加载器无法被Java程序直接引用
扩展类加载器:这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的,它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。由于扩展类加载器是由Java代码实现 的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
应用程序类加载器:这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。它负责加载用户类路径 (ClassPath)上所有的类库,是程序中默认的类加载器。
如果用户认为有必要,还可 以加入自定义的类加载器来进行拓展
双亲委派模型工作过程:一个类加载器接收到了类加载的请求,它不会自己去加载这个类,而是委托自己的父类去完成加载,会传到最顶层的启动类加载器,当启动类加载器无法加载是,子加载器才会去进行加载。
破坏双亲委派模型: