Class装载系统

Class装载系统

Class类型通常以文件的形式存在(当然,任何二进制流都可以是Class类型),只有被Java虚拟机装载的Class类型才能在程序中使用。系统加载Class类型可以分为加载、链接和初始化3个步骤。其中,链接又可以分为验证、准备和解析3个步骤。
这里写图片描述

类装载的条件

Class只有在必须要使用的时候才会被装载,Java虚拟机不会无条件的装载Class类型。Java虚拟机规定:一个类或者接口在初次使用时,必须进行初始化。这里的使用指的是主动使用,主动使用有以下几种情况:

  1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  2. 当调用类的静态方法时,即当使用了字节码invokestatic指令
  3. 当使用类或者接口的静态字段时(final常量除外),即使用getstatic或者putstatic指令
  4. 当使用java.lang.reflect包中的方法反射类的方法时
  5. 当初始化子类时,必须先初始化父类
  6. 作为启动虚拟机、含有main方法的那个类 除了以上情况属于主动使用外,其他情况均属于被动使用,被动使用不会引起类的初始化。

加载

  • 装载类的第一个阶段
  • 通过类的全限定名取得类的二进制流
  • 转为方法区数据结构
  • 在Java堆中生成对应的java.lang.Class对象

链接 -> 验证

目的:保证Class流的格式是正确的

文件格式的验证

  • 是否以0xCAFEBABE开头
  • 版本号是否合理

元数据验证(语义检查)

  • 是否有父类
  • 继承了final类?
  • 非抽象类实现了所有的抽象方法

字节码验证 (很复杂)

  • 跳转指令是否指向正确的位置
  • 操作数类型是否合理

符号引用验证

  • 符号引用的直接引用是否存在

链接 -> 准备

当一个类验证通过后,虚拟机就会进入准备阶段,在这个阶段,虚拟机会为这个类分配相应的内存空间,并设置初始值。

类型默认初始值
nt0
long0L
short(short)0
char\u0000
booleanfalse
referencenull
float0f
double0f

注意:java并不支持boolean类型,对于boolean类型,内部实现是Int,由于int的默认值是0,故对应的,boolean的默认值是false

这里举个例子:

public static int v=1;

对于这句代码:

  • 在准备阶段中,v会被设置为0
  • 在初始化<clinit>方法中,v才会被设置为1
  • 对于static final类型(常量),在准备阶段就会被赋上正确的值
  • public static final int v=1;

链接 -> 解析

解析阶段的任务就是将类、接口、字段和方法的符号引用转为直接引用。
符号引用只是一种表示的方式,比如某个类继承java.lang.object,在符号引用阶段,只会记录该类是继承”java.lang.object”,以这种字符串的形式保存,但是不能保证该对象被记载。

直接引用就是真正能使用的引用,它是指针或者地址偏移量,引用对象一定在内存。最终知道在内存中到底放在哪里。

替换后,Class才能索引到它要用的那些内容。

初始化

初始化时类装载的最后一个阶段。如果前面的步骤没有出现问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行java字节码。

初始化阶段的重要工作是执行类的初始化方法<clinit>方法,<clinit>是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。

  • static变量的赋值语句
  • static{}语句

子类的<clinit>调用前保证父类的<clinit>被调用,因此,在虚拟机中第一个被执行的<clinit>方法的类肯定是java.lang.Object

实验:

public class Test extends A
{
    static {
        System.out.println("Test");
    }
    public static void main(String[] args)
    {
        A a = new A();
    }
}

class A {
    static {
        System.out.println("A");
    }
}

输出:

A
Test

<clinit>是线程安全的

那么Java.lang.NoSuchFieldError错误可能在什么阶段抛出呢?

很显然是在链接的验证阶段的符号引用验证时会抛出这个异常,或者NoSuchMethodError等异常。

什么是类装载器ClassLoader

  • ClassLoader是一个抽象类
  • ClassLoader的实例将读入Java字节码将类装载到JVM中
  • ClassLoader可以定制,满足不同的字节码流获取方式(譬如从网络中加载,从文件中加载)
  • ClassLoader负责类装载过程中的加载阶段

ClassLoader的重要方法

这里写图片描述

系统中的ClassLoader

  • BootStrap ClassLoader(启动ClassLoader)
  • Extension ClassLoader(扩展ClassLoader)
  • App ClassLoader (应用ClassLoader/系统ClassLoader)
  • Custom ClassLoader(自定义ClassLoader)

每个ClassLoader都有一个Parent作为父亲( BootStrap除外)

JDK中ClassLoader默认设计模式 – 协同工作

这里写图片描述

自底向上检查类是否被加载,一般情况下,首先先从AppClassLoader中调用findLoadedClass查看是否已经加载,如果没有加载,则会交给父类,ExtensionClassLoader去查看是否加载,还没加载,则再调用其父类,BootstrapClassLoader查看是否已经加载,如果仍然没有,自顶向下尝试加载类,那么从 BootstrapClassLoader到 AppClassLoader依次尝试加载。

其中Bootstrap ClassLoader之中的参数 -Xbootclasspath可以指定加载的路径,这样该路径下的类也会存在于Bootstrap ClassLoader之中。

为了证明自顶向下尝试加载类,举个例子:
这里写图片描述
这里写图片描述

此时运行显示: I am in apploader

如果在建一个类,放到路劲D:/tmp/clz 之中
这里写图片描述

此时加上参数 -Xbootclasspath/a:D:/tmp/clz 运行的时候显示: I am in bootloader

证明ClassLoader自顶向下尝试加载类

加载的源码:
这里写图片描述

从代码上可以很容易看出来,首先先自己查看这个类是否被调用,如果没有找到,则调用父亲的loadClass,直到BootstrapClassLoader(没有父亲)。

我们把这个加载的过程叫做双亲模式。

双亲委托机制的作用是防止系统jar包被本地替换

双亲模式的问题

这种模式下会有一个问题:

顶层ClassLoader,无法加载底层ClassLoader的类

Java框架(rt.jar)如何加载应用的类?

比如:javax.xml.parsers包中定义了xml解析的类接口
Service Provider Interface SPI 位于rt.jar
即接口在启动ClassLoader中。
而SPI的实现类,在AppLoader。

这样就无法用BootstrapClassLoader去加载SPI的实现类。

解决

JDK中提供了一个方法:

Thread. setContextClassLoader()
  • 上下文加载器
  • 是一个角色
  • 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
  • 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

上下文ClassLoader可以突破双亲模式的局限性

举个例子:
这里写图片描述
这里写图片描述

主要就是在 cl != null的情况下,返回cl.loadClass(className)

顺便说一下:

  • 双亲模式是默认的模式,但不是必须这么做
  • Tomcat的WebappClassLoader就会先加载自己的Class,找不到再委托parent
  • OSGi的ClassLoader形成网状结构,根据需要自由加载Class
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值