一、类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
二、类加载过程(加载、验证、准备、解析、初始化)----五个过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个 部分统称为连接(Linking),这7个阶段的发生顺序如下图所示。
图中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定(多态的实现机制))。
(1) 加载:获取java.lang.Class对象,注意加载是类加载的一个过程。
(a)通过一个类的权限定名称来获取定义此类的二进制字节流。(后面的类加载器用来实现这个过程)
(b)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
(c)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
(2) 连接:
(a)校验:检查载入Class文件数据的正确性;
(b)准备:给类的类变量(即静态变量)在方法区中分配内存和设置类变量初始值(final修饰的static变量除外);
注意:不是实例变量,实例变量将在对象实例化随对象一起分配到Java堆中;其次,初始值为数据类型的零值。
最后,被final修饰的static变量,编译时Javac将会为该变量生成ConstantValue属性,在准备阶段虚拟机就会
根据 ConstantValue的设置将value赋值为给定的值。
(c)解析:将常量池内的符号引用转成直接引用(静态解析)
(3) 初始化:对类的静态变量,静态代码块执行初始化操作
在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。
关于<clinit>方法的几点说明:
a.<clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块 (static{}块)中的语句合并产生的。而且,父类的<clinit>方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
b.<clinit>方法不同于类构造函数(实例构造器<init>方法),不需要显示地调用父类,上a所述。
c.如果一个类中没有静态语句块,也没有对类变量的赋值操作,编译器可以不为这个类生成<clinit>方法。
d.虚拟机会保证一个类的<clinit>方法在多线程环境下被正确地加锁、同步。
三、类加载器
实现类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作的模块称为:类加载器。
比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。否之必不相等。
四种类加载器:
1)Bootstrap ClassLoader
启动类加载器:负责加载$JAVA_HOME中jre/lib/
里部分jar,而且必须是虚拟机按照文件名识别的,由 C++ 实现,是虚拟机自身的一部分。
2)Extension ClassLoader
扩展类加载器:负责加载Java平台中扩展功能的一些 jar 包,包括$JAVA_HOME中jre/ext/*.jar
或-Djava.ext.dirs
指定目录下的 jar 包。
3)Application ClassLoader
应用程序类加载器:负责记载 classpath 中指定的 jar 包及目录中 class。
4)Custom ClassLoader
自定义类加载器:属于应用程序根据自身需要自定义的 ClassLoader,如 Tomcat、jboss 都会根据 J2EE 规范自行实现 ClassLoader。
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从 Custom ClassLoader 到 BootStrap ClassLoader 逐层检查,只要某个 Classloader 已加载就视为已加载此类,保证此类只所有 ClassLoade r加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
双亲委派机制:
JVM在加载类时默认采用的是双亲委派机制。双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
通俗点讲就是:现在有个任务要交给老小做,老小先给老三,老三不想干,扔给老二,老二继续委托到老大,老大有能力做就自己做了,没有能力再给老二,依此下去。
为什么使用双亲委派模型:
Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
比如java.lang.Object,它存放在rt.jar中,无论哪一个类加载器加载这个类,最终都委派给处于模型最顶端的启动类加载器加载,因此Object类在程序的各种类加载环境下都是同一个类。
相反,如果没有使用双亲委派模型,由各个类加载器自行加载的话,如果用户自己写了一个java.lang.Object类,并放在程序的ClassPath中,那程序中将会出现多个不同的Object类,java体系最基础的行为无法保证,应用程序也会一片混乱。