一.关于类加载机制
- 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行效验,解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
- JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化。其中验证,准备,解析属于连接阶段。
二.加载过程
加载
- 通过一个类的全限定名来获取其定义的二进制字节流。
- 将这个字节流所代表的静态存储结构(如配置文件)转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象(HotSpot将这个Class对象放在方法区),作为对方法区中这些数据的访问入口。
(二进制流可以来源于Class文件、jar、计算生成的($Proxy)、Jsp等)
验证
- 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。即确保加载的类符合JVM的规范和安全要求。
包括文件格式验证、元数据验证、字节码验证、符号引用验证。
- 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。即确保加载的类符合JVM的规范和安全要求。
准备
- 准备阶段为static变量在方法区中分配内存空间,并设置变量的初始值(默认值)。如static int a=1,在此阶段会a被初始化为0(默认值),其他类型也是默认值,但用final修饰的类变量初始化为常量值。
解析
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
- 符号引用就是一组符号来描述目标,可以是任何字面量。
- 直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化
- 初始化是执行类构造器clinit方法(线程安全)的过程。到了这一阶段才是真正执行类中定义的Java代码。
- client方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。静态语句块中可以为在其之后定义的静态变量赋值,但不能访问。
三.类加载器
什么是类加载器
- JVM把类加载阶段中的“通过一个类的全限定名来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。用于让应用程序决定如何获取所需的类。
- JVM规定只有两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。相同的Class文件被不同的类加载器加载的类不相等。
类加载器分类
- 启动类加载器:负责加JAVA_HOME\lib 目录下的的类。
- 扩展类加载器:负责加载 JAVA_HOME\lib\ext 目录下的类。
- 应用程序类加载器:负责加载用户类路径(classpath)上指定的类库。一般情况下是程序默认的加载器。
- 自定义类加载器(处理加密)
四.双亲委派加载模型
- 类加载器之间的关系
- 我们知道类加载器有上面的四种,它们的关系如下,即双亲委派模型:
它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,这种父子关系一般通过组合关系来实现,而不是继承。
- 我们知道类加载器有上面的四种,它们的关系如下,即双亲委派模型:
- 双亲委派模型的加载过程
- 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次向上委托,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,就返回给来委托子类,子类才自己去加载。
- 采用双亲委派的好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同一个Object类,自己编写重名java.lang.Object类也同样会被启动类加载器加载(除非自定义类加载器),保证Java类型体系的正常进行,不会造成类重名引起的混乱。
- eg:能不能自己写个类叫java.lang.System?
答案(错误,提供理论答案):正常情况下不可以,但可以采取另类方法达到这个需求。
因为JVM类加载机制采用双亲委派模型,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写了一个System类,也总是使用Java系统提供的System(即爸爸加载的),自己写的System类根本没有机会得到加载。但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器、类放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。
分析:https://blog.csdn.net/tang9140/article/details/42738433