1. 类加载的过程
加载:
加载过程Java虚拟机需要完成三件事情:
通过一个类的全限定名来获取定义此类的二进制字节流——这个通常单独交给类加载器去完成
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口
验证:
这一阶段的目的是确保class文件的字节流中包含的信息符合Java虚拟机规范的全部约束要求,保证这些信息不回危害虚拟机自身的安全,主要验证两个东西:
文件格式验证
元数据验证:主要验证与父类之间的关系是否符合规范
字节码验证:验证具体程序的语义
符号引用验证:这个发生在虚拟机将符号引用转化为直接引用的时候,验证是否缺少或者进制访问外部依赖
准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。(仅包含类变量,不包含实例变量)
解析:解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口,字段,类方法,方法类型等等
符号引用:用一组符号来描述所引用的目标,只要使用时能无歧义的定位到目标即可。字面量形式明确定义在Java虚拟机规范的class文件格式中
直接引用:可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的
初始化:在该阶段,才真正意义上的开始执行类中定义的java程序代码,该阶段会执行类构造器,并根据特定的字节码指令或者一些规则初始化类
使用:使用该类所提供的功能,其中包括主动引用和被动引用
主动引用:
通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法
通过反射方式执行以上三种行为
初始化子类的时候,会触发父类的初始化
作为程序入口直接运行时(也就是直接调用main方法)
被动引用:
引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化
定义类数组,不会引起类的初始化
引用类的常量,不会引起类的初始化
卸载:方法区内存回收中对类的回收条件:
该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
加载该类的ClassLoader已经被回收;
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
2. 类加载器
类加载器分类
从Java虚拟机角度看,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用c++语言实现,是虚拟机自身的一部分;另一种就是其他所有的类加载器,这些类加载器都有Java语言实现,独立存在于虚拟机外部,并且全都集成自抽象类java.lang.classloader
从开发人员角度看,主要分为四种类加载器:
启动类加载器
扩展类加载器
应用程序类加载器(系统类加载器):加载用户类路径上所有的类库
自定义类加载器
双亲委派模型
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,只有当父加载器反馈自己无法完成这个加载需求时,子加载器才会尝试自己去完成加载
好处是Java中的类随着它的类加载器一起具备了一种带有优先级的层级关系。例如object类,最终都是委派给启动类加载器加载,因此object类在程序的各种类加载器环境中都能够保证是同一个类;反之,如果没有使用双亲委派模型,如果用户自己也编写了一个object相同全限定名的类,那么Java类型体系中最基础的行为也就无从保证
双亲委派模型的打破
1、在双亲委派模型刚引入时,面对已经存在的用户自定义类加载器的代码,做出了一些妥协,首先调用loadclass,也就是双亲委派模型的方法,如果从父类中找不到,则调用用户自己的findclass方法完成加载,这样既不影响用户按照自己的意愿去加载类,又可以保证新写出来的类加载器是符合双亲委派规则的
2、双亲委派模型自身有缺陷,如果基础类型要调用用户代码,比如JNDI服务,它的代码是由启动类加载器来完成加载的,但是启动类加载器是不可能认识,加载哪些用户代码的。
为了解决这个问题,引入了线程上下文类加载器。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器
有了线程上下文类加载器,就可以在父类加载器去请求子类加载器完成加载
3、模块化的热部署中的类加载模式
OSGi环境下,类加载器不再使用双亲委派模型的树状结构,而是使用更为复杂的网状结构。
OSGi的类加载顺序:
父容器Classloader(通常是app classloader) –> 其他bundle的Classloader(import的类所在的bundle)–> 当前bundle的Classloader–> 动态导入的包所在bundle的Classloader->类查找失败