虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是java虚拟机的类加载机制。
思考几个问题:
一,什么时候会进行类加载?
在Java中,类型的加载是在程序运行期间完成的,而且是按需加载,只会加载一次,以后从虚拟机的class实例的缓存中获取。
例如执行new操作时候,当我们使用Class.forName(“包路径+类名”),Class.forName(“包路径+类名”,classloader),classloader.loadclass(“包路径+类名”);时候就触发了类加载器去类加载对应的路径去查找*.class,并创建Class对象.
二,用什么去加载?
类加载器(见第4点)用于实现类的加载动作。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这两个类就必定不相等。
三,加载的过程是怎样的?
分为加载,验证,准备,解析,初始化五个阶段,其中验证,准备,解析三个阶段统称为连接。
1,加载
在这个过程中需要做的事:
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
(二进制流可以从很多地方获取,ZIP,网络,动态代理,jsp,数据库等等);开发人员可以自定义自己的类加载器去控制字节流的获取方式。
2,验证
对字节流进行验证,确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.1,文件格式验证
2.2,元数据验证
进行语义分析
2.3,字节码验证
主要目的是通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。在对元数据信息中的数据类型做完校验后,这个阶段将对累的方法进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
2.4,符号引用验证
这一阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将发生在解析阶段中。
符号引用验证的目的是确保解析动作能正常执行。
3,准备
正式为类变量分配内存并设置类变量系统要求的初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。仅包括类变量,不包括实例变量。但是,final修饰的变量在这个阶段就会被初始化程序员所指定的值。
4,解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
针对类,接口,字段,类方法,接口方法,方法类型,方法句柄,调用点限定符7类符号引用。
5,初始化
真正开始执行类中定义的java程序代码,执行类构造器<clinit>方法,设置程序员设置的初始值。
类构造器<clinit>方法不是类的构造函数,而是为类变量赋值和执行静态语句块。
虚拟机会保证子类的<clinit>方法执行之前,父类的<clinit>方法已经执行完毕。
触发初始化的仅有的5个情况:
1,使用new实例化对象,读取或设置一个类的静态字段,调用一个类的静态方法时
2,反射调用的时候,如果类没有进行过初始化,先触发其初始化。
3,初始化一个类的时候,发现父类还没初始化时,先触发父类的初始化
4,虚拟机启动,初始化主类
5,使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandler实例最后的结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,需要先进行初始化。
四,类加载器是什么?
从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器BootstrapClassloader,这个类加载器由c++实现,是虚拟机自身的一部分。另一种是所有其他的类加载器,由java语言实现,独立于虚拟机外部并且都继承于抽象类java.lang.ClassLoader,
包含扩展类加载器Extension ClassLoader,应用程序类加载器Application ClassLoader,用户自定义类加载器User ClassLoader.
用户自定义的无参加载器的父类加载器默认是AppClassloader加载器,而AppClassloader加载器的父加载器是ExtClassloader,但ExtClassloader的父类加载器不是BootStarpClassloader,他们之间根本不是父子关系,只是在ExtClassloader找不到要加载类时候会去委托BootStrap加载器.而且类加载器之间的父子关系一般不会用继承实现,而是使用组合来复用父加载器的代码.
类加载器之间的层次关系是双亲委派模型,其工作过程是:一个类加载器收到一个类加载的请求,先不尝试加载,先把请求委派给父类加载器,直到顶层的启动类加载器,只有当父类加载器反馈自己无法完成这个加载请求,子加载器才会尝试加载。
为什么使用双亲委派模型?
这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。