深入浅出JVM之类装载器

class装载验证流程

1.加载:

这装载类的第一个阶段,取得类的二进制流,有三种方式,一个是直接从本地class文件获取,一个是从jar包中获取,一个是通过远程调用从网络获取类的二进制流。将类的内容(常量、字段和方法信息)转换为方法区的数据结构,然后在Java堆中生产对应的java.lang.Class对象。

2.链接

2.1验证:

​ 保证Class二进制流的格式是否正确。

​ 文件格式的验证,是否以0xCAFEBABE开头,不是以这个开头的不是正确的class文件。然后还需要验证class的版本号是否合理,一般jvm是向下兼容的,也就是jdk1.8的jvm可以运行jdk1.7编译的class文件,但是jdk1.7的jvm不能运行jdk1.8编译的class文件。如果版本号不合理会抛出相应的异常信息。

1)元数据的验证:

  • 是否有父类,检查父类是否已经存在,如果不存在则不能正常加载。

  • 是否继承了final类,如果继承了final类,但是还定义了final方法,这也是不符合规则的。

  • 非抽象类如果继承自抽象类,检查是否实现了所有的抽象方法

2)字节码验证:

​ 运行检查,栈数据类型和操作码数据参数吻合。比如栈的大小为2k字节,但是class函数的大小不止2k字节,导致字节码无法运行。另外一个是跳转指令指定到合理的位置,如果跳转的偏移量超过了函数的长度,也是无法通过验证的。

3)符合引用验证:

  • 常量池中描述类是否存在

  • 访问的方法或字段是否存在且有足够权限,严格遵守public、private、protected的访问规则

2.2 准备

​ 分配内存,并为类设置初始值(在方法区中)

​ public static int lv = 1;在准备阶段中,lv会被设置为0,在初始化的<clinit>中才会被设置为1

​ public static final int lv =1;在准备阶段就会被赋上正确的值,因为final类型的值是常量,不会被变化,所以在开始就会被赋上正确的值。

2.3 解析

​ 符号引用替换为直接引用,符号引用指的是字符串,比如所有类的父类是java.lang.Object,在class的常量池里面是字符串"java.lang.Object"。符号引用不能被直接的使用,只是标识说明一下引用的是哪个类。直接引用是指指针或者地址偏移量,需要指定该对象在内存中的位置。符号引用要替换为直接引用,这样class在执行过程中才能找到正确的对象。

3.初始化

执行类构造器函数<clinit>

static变量 赋值语句

static{}块中的语句会被执行一次

子类的<clinit>在调用前保证父类的<clinit>被调用,如果子类的<clinit>先调用的话,那么父类的一些信息就会缺失,所以要保证父类的<clinit>优先调用。

另外<clinit>是线程安全的,一个线程在执行<clinit>的时候其他的线程都需要等待。

4.什么是ClassLoader

ClassLoader是一个抽象类,它的实例将读入的java字节码装载到jvm中,ClassLoader可以定制,以满足不同的字节码流(文件、网络等)获取方式。负责类装载过程中的加载阶段,它只负责将类读入到内存中,不负责类的验证以及链接过程。

ClassLoader的重要方法

public Class<?> loadClass(String name) throws ClassNotFoundException
    //根据名字装载一个类,并返回这个类的class信息
protected final Class<?> defineClass(byte[] b, int off, int len)
    //定义一个类,该方法不公开调用,参数b是class字节码的二进制内容,根据该内容返回一个Class
protected Class<?> findClass(String name) throws ClassNotFoundException
    //loadClass回调该方法,自定义ClassLoader的时候推荐重载该方法
protected final Class<?> findLoadedClass(String name)
    //寻找依据被加载的类,类加载的时候首先查找该类是否已经被加载了,如果已经加载了是不会被二次加载的。

JDK中ClassLoader默认设计模式-双亲模式

ClassLoader的分类

  • BootStrap ClassLoader(启动ClassLoader,是可以由jvm直接调用的)

  • Extension ClassLoader(扩展ClassLoader)

  • App ClassLoader(应用ClassLoader或系统ClassLoader,一般应用程序看到的这个比较多)

  • Customer ClassLoader(自定义ClassLoader,用户自定义的ClassLoader,可以实现客户化的开发)

每一个ClassLoader都有一个Parent作为父亲,但是BootStrap没有,因为他是其他所有ClassLoader的父亲。

类加载的协同工作顺序如下:

询问类是否加载的顺序:

Customer ClassLoader->App ClassLoader->Extension ClassLoader->BootStrap ClassLoader

首先看App ClassLoader里是否有加载要查询的类,如果没有则项它的父亲Extension ClassLoader询问是否加载该类,如果Extension ClassLoader也查找不到该类,则向Extension ClassLoader的父亲BootStrap ClassLoader询问是否加载该类。

类的加载顺序:

BootStrap ClassLoader->Extension ClassLoader->App ClassLoader->Customer ClassLoader

如果在BootStrap ClassLoader中也找不到该类,那么首先由BootStrap ClassLoader尝试加载该类,如果BootStrap ClassLoader成功加载该类的话,则不会继续由其他的ClassLoader加载该类。如果没有加载成功的话,那么由Extension ClassLoader继续尝试加载,以此类推。

BootStrap ClassLoader默认加载的是rt.jar或者由参数-Xbootclasspath指定路径下的jar包。

Extension ClassLoader加载%JAVA_HOME%/lib/ext/*.jar

App ClassLoader则加载Classpath下的jar包或者class文件

问题:ClassLoader的加载模式被称作双亲模式,顶层的ClassLoader无法加载底层的ClassLoader的类,也就是BootStrap ClassLoader无法加载App ClassLoader加载的类,也无法实例化底层的类。

解决:通过Thread.setContextClassLoader来获取上下文加载器,来解决顶层ClassLoader无法访问底层ClassLoader的问题,其基本思路是在顶层ClassLoader中传入底层ClassLoader的实例。

双亲模式是默认的模式,但不是必须的,比如Tomcat的WebappClassLoader就会先加载自己的Class,找不到再委托parent去加载。

5.打破常规工作模式

可以先从底层ClassLoader加载类,如果加载不到,再请求父加载器加载类。思路就是在自定义的底层ClassLoader里先查找类,如果找不到再读取类的二进制流来定义类。如果找不到类的话再请求父加载器来加载类。

6.热替换

当一个class被替换后,系统无需重启,自动更新为最新的class。

热替换需要在类被调用的时候检查该类的文件是否有更新,如果有更新的则需要重新加载,然后在调用。

发布了36 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/xjjdlut/article/details/105242046