美文网首页
Java类加载机制

Java类加载机制

作者: wangdy12 | 来源:发表于2017-11-16 10:49 被阅读0次

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

加载 Loading

类或接口C的加载过程

  • 由相应的名称获取对应的二进制字节流,可以从rar包中,网络中获取等
  • 将字节流存储转换为方法区的数据格式
  • 最后实例化一个代表该类的Class对象public static final java.lang.Class XXX,通过 XXX.class可以访问到

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

加载器

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

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

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

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

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

  • 启动类加载器BootClassLoader:是虚拟机的一部分,唯一使用C++实现的加载器,负责加载$JAVA_HOME/lib目录中的rt.jar等核心类,以及-Xbootclasspath指定的路径中的jar/zip归档文件
  • 扩展类加载器 Extensions Class Loader:加载$JAVA_HOME/lib/ext,以及java.ext.dirs属性指定路径汇中的所有类库,具体实现sun.misc.Launcher$ExtClassLoader
  • 应用程序类加载器AppClassLoader:也叫系统类加载器,加载java.class.path属性或者说是-cp / -classpath命令参数,指定路径的的所有类库,具体实现sun.misc.Launcher$AppClassLoader,如果没有自定义类加载器,这个就是程序中默认的类加载器

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()

破坏双亲委派模型

  • 覆盖掉loadClass方法,不让它先去父类检验,而改为直接调用findClass方法去加载字节的类
  • 加载SPI实现类

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动态模块系统

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进行初始化的触发条件:

  • 执行new,getstatic, putstatic, 或者 invokestatic四条 JVM 指令中至少一条
  • 调用java.lang.invoke.MethodHandle,动态语言支持相关
  • 调用反射方法,例如Class类和java.lang.reflect包内的方法
  • 初始化C的子类
  • 接口C含有非静态非抽象的方法(默认方法)时,初始化实现接口的类,对应接口C进行初始化
  • C类是虚拟机启动时要执行的主类(main函数对应的类)

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

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

相关文章

网友评论

      本文标题:Java类加载机制

      本文链接:https://www.haomeiwen.com/subject/auizmxtx.html