JVM类加载机制
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转化解析和初始化,最终形成可被虚拟机直接使用的Java类型,这个过程就叫虚拟机的类加载机制。Java中的加载、连接和初始化都是在运行期间完成的。类是在第一次使用时动态加载的,而不是一次性加载所有类。
类的生命周期
一个类型从被加载进虚拟机内存开始,到卸载出内存为止,整个生命周期会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中,验证、准备、解析称为连接。
类加载的场景
- 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时
- 使用反射包的方法对类型进行反射调用时,如果类未加载,则需要先加载
- 初始化类时,其父类未初始化
- 虚拟机启动时会初始化入口类
- …
类加载过程
类加载过程包含加载、验证、准备、解析、初始化五个阶段
1. 加载
加载过程需要完成三件事:
- 通过一个类的全限定名获取定义该类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个该类的Class对象,作为方法区这个类的各种数据的访问入口。
其中二进制字节流可以通过以下方式获取:
- 从ZIP压缩包中获取
- 从网络中获取
- 运行时计算生成
- 由其他文件生成
- …
注意:数组类型不通过类加载器创建,而是由虚拟机直接创建。
2. 验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身的安全.
3. 准备
为类中定义的类变量(被static修饰的变量)分配内存并设置类变量初始值, 使用的是方法区的内存.
注意:实例变量不会在此时分配,而是在对象实例化时随着对象一起分配在堆中。
初始化一般为零值,如private static int value = 123;
初始化后值为0;
如果类变量是常量,那么将初始化为表达式定义的值,如·private static final int value = 123;
初始化后值为123.
4. 解析
将常量池的符号引用替换为直接引用的过程。
为了支持Java的动态绑定,解析步骤可以在初始化后再开始。
5. 初始化
初始化阶段才开始执行类中静态变量定义的初始化代码。
类加载器
对于任何一个类,都必须由他的类加载器和类本身共同确定其在虚拟机中的唯一性,两个类相等,需要类本身相等并且使用同一个类加载器加载。
三个类加载器不是继承关系,而是组合关系
类加载器分类
启动类加载器(Bootstrap Class Loader)
最顶层的加载类,属于虚拟机的一部分,负责加载
\lib目录下,或者被-Xbootclasspath参数指定的路径中存放的类。 扩展类加载器(Extension Class Loader)
负责加载
\lib\ext目录中,或者被java.ext.dirs系统变量指定的路径中的类库。 应用程序加载器(Application Class Loader)
负责加载用户路径下的所有类库。如果程序中没有自定义类加载器,一般情况下就是默认的类加载器。
双亲委派模型
应用程序由上面三种类加载器共同配合实现类加载,此外还可以定义自己的类加载器。
上图展示了类加载器间的层次关系,称为双亲委派模型。除了顶层的启动类加载器外,其他加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系实现,而不是继承关系。
工作过程
如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,所以类加载请求会传递给顶层的启动类加载器,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己去加载。
好处
Java中的类随着他的类加载器具备一种带有优先级的层次关系,从而使得基础类得到同一。
例如,如果我们自己编写一个名为
java.lang.Object
类,那么加载得到的仍然是系统自带的类,我们自己编写的类可以编译通过,但永远无法得到加载。
自定义类加载器
除了BootstrapClassLoader
,其它类加载器均由Java实现,且全部继承自java.lang.ClassLoader
。因此自定义类加载器需要继承java.lang.ClassLoader
。
破坏双亲委派模型:重写ClassLoader类的loadClass()方法。