1.类加载机制
类的生命周期包含7个阶段
1.1 加载
加载指的是将类的字节码读入到内存,并为之创建一个java.lang.Class对象的过程。
分为三个步骤:
通过类的全限定名来获取定义此类的二进制字节流
将类的字节流代表的静态存储结构转为方法区的运行时数据结构
在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。
1.2 校验
此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。过程不展开说。
1.3 准备
为类的静态变量分配内存。并设置初始值。
1.4 解析
将常量值的引用替换成实际值
1.5 初始化
执行类中的静态代码块。类中静态变量赋值。
1.6 使用
代码中根据Class类型new对象或执行其它操作。
1.7 卸载
虚拟机通过垃圾回收将类信息及相关的实例数据从虚拟机内存区域中移除。
2.类加载器(获取二进制字节流)
在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的代码块就是类加载器。
虚拟机规范中并没有指明二进制字节流必须要从一个Class文件获取。这种开放的设计在很多方面得到充分运用,如:动态代理技术。JDK原生动态代理、CGLib动态代理的实例类型的字节码都不是从.class 文件中读取的,而是在运行时动态生成的。
2.1 Bootstrap ClassLoader(启动类加载器)
和JVM一样,启动类加载器 Bootstrap ClassLoader是用本地代码(一般是C语言)实现的,它负责加载核心Java Class(即所有java.*开头的类)。即存放在%JAVA_HOME%\lib目录中的类库。
Extension ClassLoader、Application ClassLoader 也是由Bootstrap ClassLoader加载的。
2.2 Extension ClassLoader(扩展类加载器)
扩展类加载器 Extension ClassLoader 由Java语言编写。负责加载扩展的Java Class(如所有javax.*开头的类和存放在%JAVA_HOME%\lib\ext目录中的类)
2.3 Application ClassLoader(应用类加载器)
应用类加载器Application ClassLoader由Java语言编写。负责加载应用程序自身的类库。开发者可直接使用。程序中默认的类加载器。
开发者可以通过继承ClassLoader父类来创建自己的类加载器。
3.双亲委派模型
简单描述下双亲委派模型:
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
使用双亲委派模型的好处:
-
一个Class不一定能被特定的类加载器加载。使用双亲委派模型,从启动类加载器 到 最初接收任务的类加载器 这条路径上的类加载器都会依次尝试加载类。可最大程度保障一个特定的类型加载成功。
-
同一个类被两个不同的类加载器分别加载,会被虚拟机识别成两个类。使用双亲委派模型可最大程度保障一个特定的类型仅被加载一次,避免冲突。
-
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
注意,双亲委派模型是Java设计者推荐给开发者的类加载器的实现方式,并不是强制规定的。