前言
我们知道Java语言的类型可以分为两大类:基本类型「primitive types」和引用类型「reference types」.
对于Java的基本数据类型是Java虚拟机定义好的.至于另一大类「引用数据类型」, Java将其分为四种类型:类
接口
数组
泛型参数
由于「泛型参数」在编译时期会被擦除, 因此Java虚拟机的类型实际上只有三种.
在类、接口、数组当中,「数组」是由Java虚拟机直接生成的, 类、接口则有对应的「字节流」.
最常见的字节流则属于由Java编译器生成的「class」文件.除此之外我们还可以在程序内部生成、直接从网络当中获取(如applet)的字节流.
这此不同形式的「字节流」都会被加载到Java虚拟机中, 成为类或者接口.
加载过程
从.class文件中的类到内存里的类, 按先后顺序需要经过加载、链接、初始化三个过程.
其中「链接过程」当中还需要进行验证.
内存中的类没有经过初始化,同样是不能能使用.
加载
「查找」是为了查找字节流,并且据此创建类的过程.对于数组来说,是没有字节流的,而是由Java虚拟机直接生成的对于其它类型来说, Java虚拟机则需要借助类加载器来完成查找字节流的的过程.
加载器bootstrapClassLoader(启动类加载器)最顶层的加载器,由c++实现
负责加载%JAVA_HOME%/lib目录下的jar包和类,或者被-Xbootclasspath参数指定的路径中的所有的类.
2. 扩展类加载器「Extension Class Loader」继承于java.lang.ClassLoader
主要负责加载%JAVA_HOME%/lib/ext/下的jar包和类,或者被java.ext.dirs系统变量所指定的路径下的jar包.
3. 应用类加载器「Application Class Loader」继承于java.lang.ClassLoader
面向用户的类加载器
负责加载当前应用classpath下的jar和类.
双亲委派模型双亲委派模型每一个类都有它对应的类加载器.
系统中的ClassLoader在协同的时候会默认启用双亲委派模型.
在类加载的过程中,首先会判断这个类是否被加载过.
被加载过 -> 直接返回
没有被加载过 -> 尝试加载
加载流程先把加载类的请求委派给该父类加载器的loadClass进行处理.
因此所有的请求最终都应该传送到最顶层的类加载器BootstrapClassLoader中.
当父类无法处理时,才需要自己处理.
当父类加载器为null时,会使用BootstrapClassLoader作为其父类加载器
双亲委派模型的优点保证了java的稳定的运行, 可以保证类不被重复加载.
jvm区分不同的类不仅仅是根据全类名, 相同的类文件被不同的类加载器加载产生的也是两个不同的类.
保证java核心api不被修改.
链接
将创建的类合成至Java虚拟机中,使之能够被执行的过程.它可以分为验证、准备及解析三个过程.验证阶段的目的是为了确保被加载的类满足虚拟机的约束条件.
准备阶段的目的是为了被加载类的字段分配内存.
Java代码中对静态字段的具体初始化则会在稍后的初始化阶段进行.
构造其它跟类层次相关的数据结构.比如说用来实现虚方法的动态绑定的方法表.
解析阶段的目的是,将符号引用解析成为实际引用.
如果符号引用指向一个未被加载的类、或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化)
初始化
为标记为常量值的字段赋值,以及执行方法的过程.
java虚拟机会通过加锁来确保类的方法仅被执行一次.
只有当初始化完成之后,类才正式成为可执行状态.
类的初始化触发情况当虚拟机启动的时候,初始化用户指定的主类.
当遇到用以新建目标类实例的new指令的时候,初始化new指令的目标类.
当遇到调用静态方法时,初始化该类的静态字段.
当遇到访问静态字段的指令时,初始化该静态字段所在的类.
子类的初始化会触发父类的初始化.
如果一个接口定义default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化.
使用反射api对某个类进行反射调用时,初始化这个类.
当初次调用MethodHandle实例时,初始化该MethodHandle指向的方法所在的类.