加载只是类加载的一部分,不要搞混了
上面最主要的就是前五个:加载、验证、准备、解析,初始化。
按部就班开始的顺序是:加载,验证、准备、初始化、卸载。
对于解析可以在初始化后再进行,这是为了java语言的动态绑定或者称为晚期绑定,,需要注意的是这五个是按照这个顺序开始的,但不是完成一个再进行下一个,可能交叉进行,通常在一个阶段中调用,激活另一个阶段
对于初始化的情况有5种情况必须初始化:
- 遇到new,getstatic,putstatic,invokestatic时
- 对类进行反射调用时
- 初始化一个类时,但发现父类没有初始化,那就先触发父类的初始化
- 虚拟机启动时,用户需要指定一个主类(比如main方法在的那个类),虚拟机会初始化这个类
- 当使用JDK1.7的动态元支持时,如果一个java.lang.invoke.MethodHadle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个类还没被初始化,就需要先触发其初始化
2、类加载的过程
类加载的过程也就是上面前5个
(1)加载
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
(2)验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害到虚拟机自身的安全
- 文件格式验证:是否符合Class文件的规范
- 元数据验证:描述的信息符合java语言规范
- 字节码验证: 确定程序语义是否合法,是否符合逻辑
- 符号引用验证:这个发生在解析阶段,将符号引用转成直接引用
(3)准备
正式为类变量分配内存并设置初始值的阶段,这些变量将在内存的方法区进行分配,
强调:
- 分配的内存仅包括类变量(被static修饰的变量)
- 这里的初始值不是程序员自己写的,而是默认的,比如
public static int value =123
初始值value时0,而不是123,在初始化时再赋给value=123
,当然也有特殊情况,比如用final修饰的静态 变量,类的字段属性表有ConstantVlaue
(可以把它当作final在Class文件字段),这样就会在准备阶段初始化value=123
(4)解析
将常量池的符号引用直接转换成直接引用
(5)初始化
开始真正执行类最终定义的java程序代码(或者说是字节码)
首先要了解一个方法 <clinit>()方法,在类中没有显示的显示出来,而且这个在一个类中没有静态语句块和没有对变量的赋值操作,那么编译器就不会生成这个方法,这里大家应该知道是干什么的了吧,
特点:
- <clinit>()由编译器自动收集类的所有类变量的赋值操作和静态语句块(static{})中的语句合并产生的,注意:静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态访问块可以赋值但是不能访问
- 不需要显示的调用父类的构造器,在执行子类的 <clinit>()之前父类的 <clinit>()肯定执行完毕
- 虚拟机会保证 <clinit>()在多线程下正确的加锁,如果多个线程同时初始化这个类,那只有一个线程初始化,其他的线程就会阻塞等待
3、类和类加载器
比较两个类是否"相等",只有在这两个类是由同一个类的类加载器加载的前提下才有意义,即使是同一个Class文件,只要类加载器不一样,那这两个类必定不相等
(1)双亲委派模型
因为这个模型的和类加载器非常有关系
从虚拟机的角度来讲,只存在两种不同的类加载器:
- 启动类加载器(Bootstrap ClassLoader):C++实现,虚拟机自身的一部分
- 所有其他的类加载器:java实现,独立于虚拟机外部,并且全部继承java.lang.ClassLoader
下面是双亲委派模型的示例
双亲委派模型除了顶层的启动类加载,其他的都是"继承"自上面(其实是组合复用),构成了具有优先级的层次关系,
工作过程: 如果一个类收到了类加载的请求,它首先不会自己去尝试加载这个类,而是交给它的父类加载器去完成,每一个层次都是如果,只有当父加载器反馈说自己完成不了,子加载器再去完成加载请求
这种模式的好处就是最终都是委派给了启动类加载器,因此每个Objec类在程序的各种类加载器的环境都是一个类,那样判断一个类是否是同一个类加载器加载的就方便多了,
有个需要注意的是:当你自己编写一个类库(jdk)中已经存在的类,将发现可以正常编译,但是不能被加载运行,那就是因为双亲委派机制,这个类已经被加载过了。
当然这种双亲委派模型集中在ClassLoder的loadClass方法中,如果父类加载器失败,报ClassNotFoundException,就会调用自己的findClass() 执行加载
(2)破坏双亲委派模型
双亲委派模型不是强制的,所以可以去更改,
第一次破坏双亲委派模型:在JDK1.2以前是继承java.lang.ClassLoader重写loadClass方法,但是1.2之后不提倡了,可以把自己的类加载逻辑写在findClass()方法里,
第二次破坏双亲委派模型:基础类又调用回用户的代码,所以专门设计一个线程上下文类加载器,像JDBC,JCE等
第三次破坏双亲委派模型:用户对程序动态性的追求,比如代码热替换,模块热部署不用重启关机就可以直接运行
有两个正好对应上面双亲委派和破坏双亲委派的例子
Tomcat:正统的类加载器架构
OSGI:灵活的类加载器架构