Java类加载机制

2017-11-16  本文已影响0人  wangdy12

加载机制是将Class文件读取到内存,并对数据进行处理,最终形成可以被JVM直接使用的Java类型的过程,主要分为加载,链接和初始化,其中链接又分为验证(Verify),准备(Prepare)和解析(Resolve)

加载 Loading

类或接口C的加载过程

数组类没有外部二进制表示,它是由Java虚拟机直接创建的,而不是由类加载器创建的,其他的类型都是使用类加载器加载类或接口的C的二进制表示形式

加载器

通过类的全限定名获取该类的二进制字节流

有两种类加载器:Java虚拟机提供的引导类加载器和用户自定义类加载器
每个用户定义的类加载器都要继承抽象类ClassLoader。自定义的类加载器可以加载来自网络,加密文件等的二进制字节流。

在Java中,一个类是由其完全限定名来标识的(包名和类名组成)。 但是一个类在JVM中的唯一标识符号,是完全限定的类名以及加载该类的ClassLoader的实例组成的,如果类加载器不同,即使是类型相同,但是Class类的以下方法返回为false

isInstance(Object obj)//  obj是对应的Class类型,等价于 obj instanceof Class
isAssignableFrom(Class<?> cls) // 对应类可以被参数类型赋值,是参数类本身或者超类

三种系统提供的常用类加载器:

ClassLoaderloadClass函数流程:

// 检查类是否已经被加载
Class<?> c = findLoadedClass(name);
//如果没有被加载,调用父加载器
if (c == null) {
    if (parent != null) {
        c = parent.loadClass(name, false);
    } else {
        c = findBootstrapClassOrNull(name);
    }
    //如果还没有找到,调用findClass自己查找
    if (c == null) 
            c = findClass(name);
}

类加载器的层次关系称为委派模型,只加载那些在parent中无法加载到的类

线程上下文类加载器,默认继承父线程的上下文加载器,最初原始线程对应的是应用类加载器
Thread.currentThread().getContextClassLoader()

破坏双亲委派模型

JDK中提供了一个ServiceLoader<T>的一个服务加载器来实现服务发现,扫描META-INF/services/service-name文件,读取其中实现服务的具体类名称

这里需要JDK定义的ServiceLoader类需要去加载对应的服务提供类的时候,双亲委派就无法实现了

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

这里采用的就是将加载任务交给线程上下文加载器进行加载

OSGi 中的每个模块(bundle)都包含 Java包和类,模块可以声明它所依赖的需要导入的其它模块的类(通过 Import-Package),也可以声明导出自己的包和类,供其它模块使用(通过 Export-Package),也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类

OSGi 中的每个模块都有对应的一个类加载器,负责加载模块自己包含的类,整体是一个网状结构,当它需要加载 Java 核心库的类时(以java开头),它会代理给父类加载器(通常是启动类加载器)来完成,也可以通过org.osgi.framework.bootdelegation显式声明某些 Java 包和类,必须由父类加载器来加载。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载

当需要更换一个bundle时,就把Bundle连同类加载器一起换掉实现代码的热替换

链接 Linking

链接包括验证(Verify),准备(Prepare)类或接口,以及对应的父类,父接口,元素类型(数组类型时),解析(Resolution)符号引用是链接部分的可选操作

验证确保接口或类的二进制表示的结构正确,对于主版本号大于50的Class文件,使用类型检查(Type Checking,和Code属性表的StackMapTable属性相关)规则进行验证,之前采用的是类型推断(Type Inference)

准备阶段涉及创建静态字段并初始化为默认值(null,false,0等),在初始化阶段才会进行静态字段的显式初始化,如果字段属性表中存在ConstantValue属性,那在准备阶段就初始化为对应的值

解析是将运行时常量池的符号引用,动态替换为具体值的过程,除了invokedynamic指令,其他指令解析一次可以重复使用。解析动作主要针对类或接口,字段,方法,接口方法,方法类型和方法句柄,调用点限定符7类的符号引用进行。

初始化 Initialization

初始化阶段执行类或接口的<clinit>函数
接口或者类C进行初始化的触发条件:

通过子类引用父类的静态字段,不会导致子类初始化
通过数组定义来引用类,不会触发此类的初始化
常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

<init> :实例初始化方法,名字是一个无效的标识符,程序不能提供,只能由编译器提供
<clinit>:类或接口的初始化方法,最多有一个该方法,参数为空。该方法是类中所有静态变量和静态块语句合并产生的,先执行父类的<clinit>方法,同时在执行该方法的时候会上锁,多个线程初始化时,只有一个线程执行

上一篇下一篇

猜你喜欢

热点阅读