1.1虚拟机的类加载机制
虚拟机的类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。在java语言里面,类型的加载、连接和初始化都是在程序运行期间完成的,java里天生动态扩展的语言特性就是依赖于运行期间动态加载和动态链接这个特点实现的。
类加载顺序:类从被加载到虚拟机内存开始,到卸载处内存为止,它的整个生命周期包括:加载,验证,准备,解析,初始化,使用和卸载。其中,验证,准备,解析3个部分合称为连接。
(1) 加载:加载是类加载过程的一个阶段,在加载阶段,虚拟机需要完成以下3件事情:通过一个类的权限定名来获取定义此类的二进制字节流;将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据的访问入口.
(2) 验证是连接的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
(3) 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量使用的内存将在方法区分配。
注意:首先,这个时候进行内存分配的仅仅包含类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中;
其次,这里所说的初始值“通常情况“下指的是数据类型的零值,特殊情况就是指如果类变量被final修饰,则初始化为该变量被指定的值。
例如:public static int i = 123; 准备阶段i 的值为 0;因为0 是int 数据类型的零值。
publicstatic final int i = 123; 准备阶段i 的值为 123;因为i 变量被 final 修饰了。
(4) 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
(5) 初始化阶段是类加载过程的最后一步。在准备阶段,变量已经赋值过一次系统要求的初始值,而在初始化阶段,执行的是类构造器的<clinit>()方法(它与类的构造函数不同)。
<clinit> 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
<clinit> 方法与类的构造函数(或者说实例构造器<init>() 方法)不同,它不需要显式的调用父类的构造器。虚拟机会保证在子类的<clinit>() 方法执行之前,父类的<clinit>()已经被执行完毕。
由于父类的<clinit>()先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
<clinit>()方法对于类活接口来说并不是必需的,如果一个类没有静态语句块,也没有对变量的赋值操作,就没有该方法。
接口与类的不同是执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量使用时,父接口才会初始化。并且接口实现类的初始化一样不会执行接口的<clinit>()。
虚拟机会保证一个类的<clinit>()方法子啊多线程环境中被正确加锁,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()完毕。
1.2 类加载器
类加载器虽然只用于实现类的加载动作,但是他在java程序中起到的作用却远远不限于类的加载阶段。对于任何一个类,都需要由他的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器都有一个独立的类名称空间。即:比较两个类是否“相等“,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载这两个类的类加载器不同,那这两个类就必定不相等。
绝大部分Java程序一般会使用以下3中类加载器:
启动类加载器:这个类加载器负责将存放在<JAVA_HOME>\lib 目录中的类库加载到虚拟机内存中。
扩展类加载器:这个加载器将负责加载<JAVA_HOME>\lib\ext目录中的所有类库。
应用程序类加载器:这个类加载器是ClassLoader 中的getSystemClassLoader() 方法的返回值,所以一般也称为系统类加载器。他负责加载用户类路径(ClassPath)上的所指定的类库。
类加载器之间的层次关系称为双亲委派模型。双亲委派模型,除了顶层的启动类加载器外,其余类的加载器都应当由自己的父加载器子父关系不是采用继承,而是组合。
双亲委派模型的工作过程是:如果有一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是交给器父加载器去完成,每一个层次的类加载器都是如此,因此,所有的类加载器的请求都首先传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求时(他的搜索范围内没有找到该类),子加载器才会去尝试自己加载该类。