zoukankan      html  css  js  c++  java
  • 深入探究jvm之类装载器

    一、class装载验证流程

    1、加载

      1)、取得类的二进制流。

      2)、转为方法区数据结构。

      3)、在Java堆中生成对应的java.lang.Class对象。

    2、链接--验证(目的:保证Class流的格式是正确的)

      1)、文件格式的验证:是否是0xCAFEBASE开头、版本号是否正确等。

      2)、元数据验证:是否有父类、是否继承了final类、非抽象类是否实现了所有的抽象方法等。

      3)、字节码验证(最复杂):运行检查、栈数据类型和操作码数据参数是否吻合、跳转指令是否指定到合理的位置。

      4)、符号引用验证:常量池中描述类是否存在、访问的字段和方法是否存在且有足够的权限。

    3、链接--准备

      分配内存,并为类设置初始值(在方法区中),举个栗子:

      ①public static int a = 100;在链接--准备阶段中,a会被设置为0(int 类型的默认值),在初始化的<clinit>中才会被设置为1。

      ②public static final int a = 100;对于static final 类型,在链接--准备阶段就会被赋上正确的值,a被设置为100。

    4、链接--解析

      符号引用(字符串引用对象不一定被加载)替换为直接引用(指针或者偏移量,引用对象一定存在于内存中)。

    5、初始化

      1)、执行类构造器<clinit>,包括static变量赋值语句和static块。、

      2)、子类的<clinit>调用前保证父类的<clinit>被调用。

      3)、<clinit>方法是线程安全的,同步执行。

    二、什么是ClassLoader?

      ClassLoader是一个抽象类,它的实例将读入的Java字节码装载到jvm中,ClassLoader可以实现定制,以满足不同的字节码流的获取方式,主要负责类装载过程中的加载。

       1)ClassLoader中重要的方法:

    public Class<?> loadClass(String name) throws ClassNotFoundException
    载入并返回一个Class
    
    protected final Class<?> defineClass(byte[] b, int off, int len)
    定义一个类,不公开调用
    
    protected Class<?> findClass(String name) throws ClassNotFoundException
    loadClass回调该方法,自定义ClassLoader的推荐做法
    
    protected final Class<?> findLoadedClass(String name) 
    寻找已经加载的类
    

       2)JDK中ClassLoader默认的设计模式分类:

       ①BootStrap ClassLoader(启动ClassLoader)

       ②Extension ClassLoader (扩展ClassLoader)

       ③App ClassLoader(应用ClassLoader/系统ClassLoader)

       ④自定义ClassLoader

       上图展现了类查找和加载的次序,这样我们很容易就会想到存在一个问题:顶层的ClassLoader是无法加载底层ClassLoader的类,只就是“双亲问题”。那么Java框架(也就是rt.jar)如何加载Classpath下应用的类呢?

      举个栗子进一步说明问题:javax.xml.parsers包中定义了xml解析的类接口,这些类接口(Service Provider Interface)都位于rt.jar,即接口的定义(以及类的工厂方法)都在Bootstrap ClassLoader中,其实这个SPI的实现(非抽象类)都在AppLoader之中,JDK要求Bootstrap ClassLoader能够加载Classpath下的类,这显然是不满足上图的要求的。

      JDK为了解决这个问题在Thread类中定义了一个静态方法,Thread.setContextClassLoader()。这是一个上下文加载器,是一个“角色”,并不是一个真正的ClassLoader,它只是承担了特殊的任务,用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题。基本思想是在顶层ClassLoader中传入一个底层的ClassLoader实例。下面的代码来源于rt.jar中javax.xml.parsers.FactoryFinder展示如何在启动类加载器中加载AppLoader的类突破“双亲模式”问题。

    static private Class getProviderClass(String className, ClassLoader cl,
            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
    {
        try {
            if (cl == null) {
                if (useBSClsLoader) {
                    return Class.forName(className, true, FactoryFinder.class.getClassLoader());
                } else {
                    cl = ss.getContextClassLoader();
                    if (cl == null) {
                        throw new ClassNotFoundException();
                    }
                    else {
                        return cl.loadClass(className); //使用上下文ClassLoader
                    }
                }
            }
            else {
                return cl.loadClass(className);
            }
        }
        catch (ClassNotFoundException e1) {
            if (doFallback) {
                // Use current class loader - should always be bootstrap CL
                return Class.forName(className, true, FactoryFinder.class.getClassLoader());
            }
    …..
    

      双亲模式是默认的模式,但不是必须要这么做,比如Tomcat的WebappClassLoader就会先加载自己的class,找不到再委托parent,再如,OSGi(模块化,热加载)的ClassLoader形成网状结构,根据需要自由加载Class。

      破坏双亲模式的例子,先从底层的ClassLoader加载。OrderClassLoader的部分实现:

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class re=findClass(name);
        if(re==null){
            System.out.println(“无法载入类:”+name+“ 需要请求父加载器");
            return super.loadClass(name,resolve);
        }
        return re;
    }
    

      findClass(String name )实现如下:

    protected Class<?> findClass(String className) throws ClassNotFoundException {
    Class clazz = this.findLoadedClass(className);
    //每个类只加载一次,会查找、定义、加载 if (null == clazz) { try { String classFile = getClassFile(className); FileInputStream fis = new FileInputStream(classFile); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); //-----------------省略部分代码-------------------// fis.close(); byte[] bytes = baos.toByteArray(); clazz = defineClass(className, bytes, 0, bytes.length); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return clazz; }

      

      

  • 相关阅读:
    Yum安装MySQL
    Java最小化镜像制作
    Docker CE安装
    每月最后一周的周六晚上21:00执行任务-crontab
    每10秒执行定时任务-crontab
    可复制领导力-回顾收录
    逻辑数据库设计
    5e赋能核心文化
    python 学习自学
    德鲁克的“五项主要习惯”
  • 原文地址:https://www.cnblogs.com/liuyk-code/p/10364158.html
Copyright © 2011-2022 走看看