1.简介
基本过程:加载器加载class文件,子阶段可分为加载阶段、链接阶段和初始化阶段。类加载子系统通过存储设备或者网络加载特定标识的class文件,例如class文件十六进制的前4个字符为ka fe ba be,类加载系统只负责加载class文件,是否能够正常运行是由运行引擎决定的。class文件加载后,其信息存放在运行时数据区中的方法区,除了类信息保存在方法区,一些常量信息也会保存在方法区。
2.详细过程
-
加载阶段
- 通过class文件的路径获取文件的字节流,将字节流中的存储信息转换成方法区的运行时数据结构,并生成一个java.lang.Class对象,该对象作为访问该class文件对应方法去数据结构的入口。class文件加载后主要暴露常量池,方法,字段,接口,变量等信息。
-
class文件来源
- 本地磁盘
- 网络
- zip包中获取,jar,war包
- 运行时动态生成,动态代理
- 其他文件生成
- 从数据库中读取class文件
- 加密的文件经过解密后的文件
-
链接阶段
- 链接分为三个阶段:验证,准备,解析
-
验证
- 验证加载的class文件是否符合当前jvm要求,包含文件格式验证,元数据验证,字节码验证和符号引用验证
-
准备
- 为类变量分配内存并且设置初始值,该初始值为变量类型的初始值,如果int为0,boolean为false等
- 如果类变量被final修饰了,那么该变量会在编译阶段分配了,在此阶段会初始化值
- 类实例变量不会在此时分配内存
-
解析
- 将常量池中的符号引用转化为直接引用
- 符号引用就是jvm规范中说明的符号如#1,#2,#3,这些符号的引用转换成具体的直接引用,直接引用指目标的指针、相对偏移量或者一个间接定位到目标的的句柄
- 解析主要针对类或者接口,字段,类方法,接口方法等等。
-
初始化
- 初始化的过程就是执行类构造方法<clinit>()的过程,<clinit>()方法不需要进行明码编写,该方法是编译器自动增加的,编译器会收集需要初始化的类变量和需要执行的静态代码块。如果类中没有类变量存在,就不会存在<clinit>()方法
- <clinit>()中的方法的赋值的顺序与类中类变量和静态代码块的顺序有关。在类对象中,如果静态代码块里面赋值一个类变量,然后显式声明上面的类变量,那么在该类变量的值将会是显式声明的值。
- 由于虚拟机对<clinit>()方法进行加锁,当有多个线程进行加载同一个class文件的时候
3.类加载器
-
分类
- 引导类加载器:bootstrap classloader(使用c++编写的,在java程序中获取不到该加载器,得到的值为null)
- 自定义加载器: 继承或间接继承ClassLoader抽象类的加载器,主要有ExtClassLoader和ApplicationClassLoader(使用java编写的)
- 自定义加载器:可以继承ClassLoader或者ExtClassLoader类加载器,自定义加载器。
-
boostrap ClassLoader
- 用来加载java核心核心库(如rt.jar,resources.jar),只有加载java,javax,sun开头的包中的类
- 扩展类加载器和应用程序加载器是由引导类加载器加载的。
-
应用程序加载器Application ClassLoader
- java语言编写的,继承ClassLoader抽象类,其父加载器为bootstrap ClassLoader加载器。
- 加载java.ext.dirs系统属性指定的目录或者jdk的安装目录jre/libs/ext子目录下的类
-
应用程序加载器 Application Class Loader
- 继承于ClassLoader抽象类,其父加载器为ApplicationClassLoader
- 一般加载环境变量claspath或者java.class.path下的类
- 一般项目中的类都是由该类加载完成的
4.自定义加载器
-
使用:
- 继承ClassLoader、ExtClassLoader或者ApplicationClassLoader,可实现自定义加载器,自定义加载器最终返回的是Class对象。
-
使用场景
- 隔离加载类,一些中间件需要加载特定的jar,这些jar与已有的冲突,中间件就会自定义加载器加载这些jar.
- 修改类的加载方式
- 扩展加载源
- 加密jar文件:对jar文件加密后,自定加载器解密后才加载。
5.双亲委派机制
- jvm存在引用加载器,扩展加载器,应用程序加载器,当需要加载一个类的时候,扩展加载器的父加载器为引用加载器,应用程序加载器的父加载器为扩展加载器,如果需要加载一个类,首先查看父加载器能否进行加载,父加载器再请求父加载器进行加载,如果父加载器加载不了,才会让子加载器进行加载,如果父加载器能够进行加载就返回加载的class对象。
-
为什么使用双亲委派机制
- 保证一个类在多个加载器中,只会被一个加载器加载
- 保证程序安全:由于三个加载器加载类的目录不同,当程序中自定义java核心类时,能够保证核心类不被修改。如自定义java.lang.String类,当加载类的时候,bootstrap加载器就会识别路径为核心类,就会报属于核心类错误,不能够被重新加载,不同级别的加载器加载不同安全级别的类,保证程序在该沙箱中安全。
jvm中是如何保证两个Class对象都是相同的:
- 加载class对象的路径是相同的
- 并且是由同一个加载器加载的,Class是由什么加载器加载的信息在class对象中存在。
6.类的主被动加载
-
主动使用
- 创建类的实例
- 访问类或接口的静态变量,或者赋值
- 调用类静态方法
- 反射
- 初始化类的子类
- 被表明jvm启动类的类
- 被动使用
不是主动使用的都是被动使用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。