本文介绍了JVM中的重要内容类加载机制。Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
类的生命周期
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历 加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化
(Initialization)、使用(Using)和卸载(Unloading 七个阶段。
其中验证、准备、解析三个部分统称为连接(Linking)。
加载,验证,准备,初始化,卸载这五个阶段的顺序是确定的。这些过程可能是交叉的。
加载
需要完成三件事:
- 通过类的全限定名,查找并加载类文件的二进制数据;
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
连接:
讲读入数据合并到JVM内存
- 验证:确认正确性。相当大的比重。
- 准备:为类的静态变量分配内存,并设置初始值。在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 解析:把常量池的符号引用转换为直接引用。
- 验证:类文件格式检查,元数据验证,字节码验证,符号引用验证。
- 准备:首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中
- 解析:符号引用变成直接引用。
初始化
为类的静态变量赋值,或者说执行类构造器<clint>
方法。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,
类初始化:
初始化时机(主动引用)
- 创建类实例在new实例化对象,
- 读取或设置类或者接口的静态字段,
- 调用类的静态方法。
- 反射某个类。
- 如果初始化类式其父类没有被加载要先加载父类。
- 当虚拟机启动时,要先加载主类。
- 使用JDK 7新加入的动态语言支持,和JDK8的默认方法时…
被动引用
当子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
数组定义引用类,不会触发类的初始化。
final修饰常量会在编译阶段被存入调用类的常量池,不会触发定义常量的类的初始化
接口初始化:
如果是接口,与类初始化时机的区别在于:
- 初始化类时候,不会先初始化它实现的接口
- 初始化接口时,不会初始化父接口
- 只有首次使用接口变量或者调用接口方法,才会初始化。延迟加载
其余的一样
卸载
当Class对象不再被引用,对应在方法区的数据会被卸载。
类加载器
三种加载器:启动类加载器(基础启动null)、平台类加载器(平台相关)、应用程序类加载器。或者自定义类加载器。
双亲委派模型
站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
定义
双亲委派模型除了启动类加载器,都要有自己的父加载器。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的
加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
优点:
- 保证了安全性,系统类被保护
例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。
代码实现:
先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载。
命名空间
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
破坏双亲委派
存在问题:父加载器无法识别子加载器加载的资源,因为在某些情况下父类加载器需要委托子类加载器去加载class文件。或者热替换。
解决办法:引入线程上下文类加载器,通过Thread的setContextClassLoader()设置。