JVM:类加载机制

前言

JAVA虚拟机中的类加载机制非常重要,在关于JVM的面试中经常问到,因此今天整理一下相关的知识点,一方面为之后的面试做准备,另一个方法也是扩展一下知识面。

类的生命周期

关于类的生命周期,直接上图看下。
在这里插入图片描述
类从加载进虚拟机内存,到从虚拟机卸载的整个生命过程包括 加载、验证、准备、解析、初始化、使用、卸载。
加载、验证、准备、初始化和卸载的顺序是固定的,但是解析不一定,它可以出现在初始化之后,是为了支持运行时动态绑定【多态】。

类的初始化时机

1、new对象、调用静态属性(非final)、调用静态方法
2、class.forName,通过反射加载类,这个类如果没有加载过
3、当要初始化的类有父类,则先初始化父类
4、执行main方法的类
5、多态,动态绑定【这个还要研究下,不理解。。】

类加载全过程

加载

1、通过类的全限定路径名获取定义该类的二进制字节流
2、将这个二进制字节流表示的静态存储结构转化成方法区的运行时数据结构
3、在内存中生成一个java.lang.Class对象作为方法区中该类的访问入口

注意:class对象比较特殊,虽然称之为对象,但是放在方法区内,作为各类的访问入口

验证

目的:确保class对象中包含的字节流信息符合当前虚拟机的要求,并且不会危害虚拟机的安全。
步骤:包含
文件格式验证:作用,确保字节流符合Class文件的规范

元数据验证:
作用,确保符合java语言 的语法。
例如:是否有父类。是否继承了不该继承【final】的类。是否重写了抽象类和接口里的方法。是否覆盖了父类的final字段。是否出现方法参数一致,返回值不一致。

字节码验证:
作用:验证方法体中的语法错误

符号引用验证:
作用:确保符合引用是找得到的。

准备

作用:为静态变量赋予零值
假设
public static int i=1;
所谓赋予的零值是0,并不是1.

解析

作用:符合引用解析为直接引用

初始化

作用:
自上而下初始化静态变量【赋值】、执行静态块语句

类加载器

类与类加载器

注意:
比较两个类是否相等的前提是通过同一个类加载器加载。
即使是同一个class文件,同一个虚拟机,由两个不同的类加载器加载,这两个class对象也是不相等的。

JVM中三种加载器

1、启动类加载器:加载 %JAVA_HOME%\lib下的类
2、扩展类加载器:加载 %JAVA_HOME%\lib\ext下类
3、应用加载器:加载classPath下的类

双亲委派机制

在这里插入图片描述
如图层次模型即双亲委派机制:
简单来说,双亲委派机制要求除了顶层的启动类加载器之外,其他的类加载器必须要有父加载器。

双亲委派机制的工作流程

1、如果一个类加载器收到加载类的请求,首先不会自己去加载,而是向上请求父加载器去加载
2、每一层都是如此,如果有父加载器,就向上传递请求
3、如果父加载器加载不了,子加载器才会尝试去加载。

为什么要双亲委派机制?

保证同一个全限定路径名的类字节流,无论被哪个加载器加载,最终都由最上层的加载器加载,保证不会出现多个不同的 全限定路径名 一致的class对象。
举例:
java.lang.Object ,存放在rt.jar,无论哪个加载器去加载,最终都委派给顶层的启动类加载器,保证一致。

相反,如果没有双亲委派机制,自己在classPath下写一个java.lang.Object,通过不同的类加载器去加载的时候,可能在内存中出现多个Object的class对象,导致混乱。

打破双亲委派机制

双亲委派模型很好的解决了各个类加载器加载基础类的统一性问题。即越基础的类由越上层的加载器进行加载。
若加载的基础类中需要回调用户代码,而这时顶层的类加载器无法识别这些用户代码,怎么办呢?这时就需要破坏双亲委派模型了。
下面介绍两个例子来讲解破坏双亲委派模型的过程。

JNDI破坏双亲委派模型
JNDI是Java标准服务,它的代码由启动类加载器去加载。但是JNDI需要回调独立厂商实现的代码,而类加载器无法识别这些回调代码(SPI)。
为了解决这个问题,引入了一个线程上下文类加载器。 可通过Thread.setContextClassLoader()设置。
利用线程上下文类加载器去加载所需要的SPI代码,即父类加载器请求子类加载器去完成类加载的过程,而破坏了双亲委派模型。

Spring破坏双亲委派模型
Spring要对用户程序进行组织和管理,而用户程序一般放在WEB-INF目录下,由WebAppClassLoader类加载器加载,而Spring由Common类加载器或Shared类加载器加载。
那么Spring是如何访问WEB-INF下的用户程序呢?
使用线程上下文类加载器。 Spring加载类所用的classLoader都是通过Thread.currentThread().getContextClassLoader()获取的。当线程创建时会默认创建一个AppClassLoader类加载器(对应Tomcat中的WebAppclassLoader类加载器): setContextClassLoader(AppClassLoader)。
利用这个来加载用户程序。即任何一个线程都可通过getContextClassLoader()获取到WebAppclassLoader。

如果不想打破双亲委派模型,就重写ClassLoader类中的findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。而如果想打破双亲委派模型则需要重写loadClass()方法。

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/85011144