类加载的整个过程
加载、验证、准备、解析、初始化。
1. 加载
加载阶段是类加载过程的第一个阶段。他们俩不要混淆。在加载阶段。虚拟机需要完成三件事。
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
类的加载的最终产品是位于堆区中的Class对象以及位于Java方法区的类型信息。 Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
2. 验证
验证阶段是为了保护虚拟机自身安全,防止恶意class文件字节码。
在生产环境中可以考虑使用 -Xverify:none 参数来关闭验证措施,缩短虚拟机类加载时间
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用
3. 准备
将静态变量分配内存并设置静态变量初始值的阶段。注意不包括实例变量。实例变量将会在对象实例化随对象一起分配在Java堆中。
4. 解析
将常量池中的符号引用转成直接引用
5. 初始化
激活类的静态变量的初始化Java代码和静态Java代码块。
类加载的时机
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)
- 调用类的静态方法
- 反射(Class.forName("com.lyj.load"))
- 初始化一个类的子类(会首先初始化子类的父类,不适用于接口)
- JVM启动时标明的启动类。它是类初始化的源头!!
类加载器
说明:将“通过一个类的全限定名来获取描述该类的二进制字节流”的动作放到Java虚拟机之外来实现,以便应用程序自己决定如何获取所需要的类。实现这个动作的代码就被称为类加载器。
类加载器存在的目的:
区分同名的类。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确定其在Java虚拟机中的唯一性。每个类加载器都拥有一个独立的类名称空间。
更简单一点来说:对于同一个class文件,被不同的类加载器加载,他们是不相等的。这里的相等包括equals方法,isInstance方法,instanceof关键字等判断。
双亲委派模型的原理:
一个类加载器收到类加载请求后,它首先不会自己去尝试加载这个类,而是将这个请求委托给父类加载器完成,因此所有的加载请求最终都是第一个执行最顶层启动类加载器中。只要当父加载器没有完成加载请求(搜索不到)时,子加载器才会尝试自己去完成加载。
双亲委派模型的作用:
保证Java加载的稳定运行,保证一些基础类就只能由对应的一个加载器去加载!,如果没有双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在ClassPath中,那么系统就会出现多个不同的Object类。出现混乱。
启动类加载器
Bootstrap Class Loader
启动类加载器,它负责加载lib目录下且指定文件名为rt.jar、tool.jar等的类库。
拓展类加载器
Extension Class Loader
拓展类加载器,它负责加载ext目录下的类库。
应用程序类加载器
Application Class Loader
应用程序类加载器, 它负责加载用户路径上的所有类库。如果应用程序中没有自定义过自己的类加载器,一般就是这个默认的类加载器。
自定义类加载器
使用自定义加载器的时候,自定义加载器一般会符合双亲委派模型。
补充说明
方法区
方法区是各个线程共享的区域,存放类信息、常量、静态变量。
java堆
也是线程共享的区域,所有类的实例均放在这个区域。包括Java.lang.Class实例
java栈
每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈。
参考文章
https://zhuanlan.zhihu.com/p/25228545
https://blog.csdn.net/gjanyanlig/article/details/6818655/