文章目录
1 类加载器
1.1 概述
- 类加载器从文件系统或者网络中等途径加载class文件。
- 类加载器只负责classs文件的加载,至于它是否可以运行由执行引擎(Execution Engine)决定。
- 加载的类信息存放在方法区,除了类的信息之外,方法区还会存放运行时常量池(Run-Time Constant Pool)、字符串字面量和数字常量,而这些信息的来源是class中常量池(Constant Pool)的映射。
2 类的加载过程
在JVM中,类的加载过程分类三个阶段:
- 加载阶Loading。
- 链接 Linking。
- 初始化 Initialization。
2.1 加载
-
通过类的全限定名获取定义类的二进制字节流。
-
将这个字节流的静态存储结构转化为方法区的运行时数据结构。
-
在内存中生成一个代表该类的对象,作为方法区这个类的各种数据的访问入口。
加载的途径: -
从本地文件系统加载
-
通过网络获取,例如:Web Applet。
-
从zip压缩包获取,成为以后jar和wer格式的基础。
-
运行时计算生成,例如:动态代理技术。
-
由其它文件生成,例如:JSP应用。
-
从专有数据库获取class文件,很罕见。
-
从加密文件中获取,防止class文件被反编译的保护措施。
2.2 链接
类的链接分为三个步骤:
2.2.1 验证 Verify
- 确保class文件的字节流中包含的信息符合当前虚拟机的要求,保证被加载的类正确性,不会危害虚拟机安全。
- 主要包括四种验证:文件格式验证、元数据验证、字节码验证和引用符号验证
2.2.2 准备 Prepare
- 为类变量分配内存并且设置初始值,即零值而不是赋值。
- 不包含final修饰的static,因为final在编译的时候就分配好了,准备阶段会显示初始化。
- 实例变量不会被在准备阶段初始化,类变量会分配在方法区,而实例变量会随着对象一起分配到堆中。
2.2.3 解析 Resolve
- 将常量池内的符号引用转为直接引用
什么是符号引用和直接引用
符号引用:符号引用是以一组符号来描述所引用的目标,符号可以使任何形式的字面量,只要使用时能无歧义的定位到目标即可。
直接引用:直接引用可以使直接指向目标的指针、相对偏移量或者是一个能简介定位到目标的句柄。
2.3 初始化
- 除了加载阶段用户可以通过自定义加载器参与之外,其余步骤都是由虚拟机主导和控制的。
- 执行类构造器方法()的过程,此构造器不同于父类构造器,若类有父类,则JVM会先执行父类的()
3 类加载器分类
JVM支持两种类型类加载器
- 引导类加载器 Bootstrap ClassLoader
- 自定义类加载器 User-Defined ClassLoader
从概念上讲,自定义加载器一般由程序中开发人员自定义的类加载器,但是JVM是这样分类,将所有派生抽象类ClassLoader的类加载器都分化为自定义加载器。
图上的四种类加载器并不是包含和继承关系,而且所说的父类加载器或子类加载器只是指的这种包含关系,并不是面向对象的继承关系。
3.1 JVM自带的类加载器
3.1.1 启动类加载器 Bootstrap ClassLoader
也可称为引导类加载器
- 这个类加载器使用的C/C++实现,嵌套在JVM内部。
- 它是用来加载Java核心库(
‘JAVA_HOME’/jre/lib/rt.jar、resouces.jar、sun.boot.class.path
以及及-Xbootclasspath参数指定路径下的类库),用于提供JVM自身需要的类。 - 不继承java.long.ClassLoader,没有父加载器。
- 加载扩展类加载器和应用程序加载器,并指定为它们的父类加载器。
- 出于安全,启动类加载器只加载包名为java、javax和sun等开头的类。
3.1.2 扩展类加载器 Extension ClassLoader
- 有Java语言编写,由
sun.misc.launcher$ExtClassLoader
实现。 - 派生于ClassLoader类。
- 父类加载器为启动类加载器。
- 从
java.ext.dirs
系统变量所指定的目录或者从JDK安装路径下jre/lib/ext目录(扩展目录)下加载类库。如果用户创建的jar放在此路径也会由扩展类加载器加载。
3.1.3 应用程序类加载器 Application ClassLoader
- Java语言编写,由
sun.misc.launcher$AppClassLoader
实现。 - 派生于ClassLoader类。
- 父类加载器为扩展类加载器。
Classpath
以及-classpath、-cp
指定目录所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。- 该类加载器是程序中默认的类加载器,也就是说,一般的Java应用类都是用她来完成加载的。
- 通过ClassLoader#getSystemClassLoader()可以获取到该类加载器。
3.2 用户自定义的类加载器
在开发中,类的加载器几乎是有上述三种类加载器互相配合执行的,在必要时,也可以自定义类的加载器来定制类的加载方式。
为什么要使用自定义类加载器?
- 隔离加载类
- 修改类的加载方式
- 扩展加载资源
- 防止源码泄露
4 双亲委派机制
JVM对class文件采用的是按需加载,也就是说,当这个类需要使用的时候该类才会被加载到内存生成class对象。并且在加载类的时候,JVM采用的是双亲委派机制,即把加载请求交给父类处理,这是一种任务委派模式。
4.1 工作原理:
- 如果一个类加载器收到了类加载请求,如果有父类加载器,会把请求委托给父类加载器。
- 如果父类加载器还存在父类加载器,则进一步向上委托,一次递归,直到顶层启动类加载器。
- 如果父类加载器可以完成加载任务,则返回成功;若不够完成加载,返回失败,子加载器才会尝试去加载。
4.2 优势
- 避免类重复加载。
- 保护程序安全,防止篡改核心API。