ClassLoader类加载器

ClassNotFoundException,经常在开发中出现,原因就是加载的过程中找不到这个类,在第一次主动使用的时候报出的异常。

路径

在开发之前需要配置路径,我们一般做了JAVA_HOME,PATH,CLASSPATH,这三项配置。

JAVA_HOME

java_home 对应了java的jdk路径,我的路径安排在D盘

C:\Users\Administrator>echo %JAVA_HOME%
D:\Java\jdk-10.0.1

PATH

C:\Users\Administrator>echo %PATH%
C:\Python27\;
C:\Python27\Scripts;
D:\Python\Scripts\;
D:\Python\;
D:\Java\jdk-10.0.1\bin;
D:\Program Files\nodejs\;
C:\ProgramData\chocolatey\bin;
D:\Java\jdk-10.0.1\jre\bin;

可以看到,PATH中包含了很多路径,其中就包括bin和jre\bin路径

CLASSPATH

C:\Users\Administrator>echo %CLASSPATH%
D:\Java\jdk-10.0.1\lib;
D:\Java\jdk-10.0.1\lib\tools.jar

可以看到,CLASSPATH中包含了很多路径,其中就包括lib路径和lib下的tools.jar

ClassLoader

通过一个类的全限定名来获取描述此类的二进制字节流,简单说就是加载class文件,将class的二进制数据转化JVM运行时内存中的对象。
从JAVA虚拟机的角度看,只有两种ClassLoader,用C++实现的BootStrapClassLoader和用JAVA实现的其他ClassLoader。 从开发者角度看,有三种ClassLoader,分别是,BootStrapClassloader、ExtensionClassLoader、ApplicationClassLoader,他们分别加载的class路径不同。

  1. BootStrapClassLoader
    根加载器,主要加载%JRE_HOME%\lib下的jar和class。通过启动jvm时指定-Xbootclasspath也可以改变BootstrapClassLoader的加载目录。
  2. ExtensionClassLoader
    扩展类加载器,加载%JRE_HOME%\lib\ext目录下的jar和class,也可以加载-D java.ext.dirs选项指定的目录
  3. AppClassLoader
    系统加载器,加载当前应用的bin目录下所有生成的class

双亲委托

不同的路径,对应不同的加载器,加载器如何有效的去加载class?通过双亲委托模式!

每个class都有个classloader,classloader也有父classloader,如果classloader.getparent()为null,并不是说他没有父加载器,而是说父加载器是BootStrapClassLoader,
appclassloader的父加载器是extclassloadder,extclassloadder的父加载器是BootStrapClassLoader。

在加载的时候其实和view的点击事件分发一样。先dispatch,然后handle。
1.在缓存中查看是否有,如果有直接从缓存获取,否则交给父加载器去加载
2.递归1
3.如果BootstrapClassloader在自己路径(sun.misc.boot.class)下找不到class,则告诉孩子加载器,你去加载吧,我在我这找不到。
4.如果ExtClassLoader在自己路径(java.ext.dirs)下找不到class,则告诉孩子加载器,你去加载吧,我在我这找不到
5.最终appclassloader去自己路径(java.class.path)下加载,如果appclassloader也找不到class则说明class损坏,或者不存在。

传递性

当子类(继承、实现、实例化)被加载的时候,加载器是父类的加载器。

加载方式

1.隐式加载
没有代码,不调用classloader.loadclass(),由jvm自动加载
2.显式加载
调用classload.loadclass()或者Class.forName();

源码分析

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

1.通过findloadedclass()方法查找是否已经加载过
2.如果加载过了直接返回,否则查看parent是否为null
3.parent为null的话直接调用findbootstrapclassornull去判断,否则调用parent.loadclass去加载
4.如果parent.loadclass仍然没有找到则调用自定义的classloader去加载

自定义classloader

上面的三个classloader都指定了加载的class的路径,如果要加载其他路径的class,我们就需要自定义classloader
自定义classloader的时候我们尽量只重写findclass方法,不要尝试去重写loadclass

1.自定义ClassLoader继承ClassLoader
2.重写findclass方法
3.在findclass方法中调用ondefine返回class对象

自定义ClassLoader测试代码

//1.创建测试对象
package zjl.com.classloadermodule;

public class Test {
    public void TestMethod(){
        System.out.println("TestMethod2222");
    }
}
//2.自定义ClassLoader
package zjl.com.classloadermodule;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    public String libPath;

    public MyClassLoader(String libPath) {
        this.libPath = libPath;
    }

    @Override
    protected Class<?> findClass(String s) throws ClassNotFoundException {
        File file = new File(libPath,s);

        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            int len = 0;
            while ((len = fileInputStream.read())!= -1){
                byteArrayOutputStream.write(len);
            }
            byte[] data = byteArrayOutputStream.toByteArray();

            byteArrayOutputStream.close();
            fileInputStream.close();

            return defineClass(s,data,0, data.length );
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(s);
    }
}

//3.测试,如果正常在Terminal中应该输出TestMethod2222
TestMethod2222
Process finished with exit code 0

证明加载了Test.class,测试结果符合预期,这也可以作为热修复的基础例子之一。

ThreadContextClassLoader 线程上下文加载器

public
class Thread implements Runnable {
    
    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;
    
    /**
     * Returns the context ClassLoader for this Thread. The context
     * ClassLoader is provided by the creator of the thread for use
     * by code running in this thread when loading classes and resources.
     * If not {@linkplain #setContextClassLoader set}, the default is the
     * ClassLoader context of the parent Thread. The context ClassLoader of the
     * primordial thread is typically set to the class loader used to load the
     * application.
     *
     * <p>If a security manager is present, and the invoker's class loader is not
     * {@code null} and is not the same as or an ancestor of the context class
     * loader, then this method invokes the security manager's {@link
     * SecurityManager#checkPermission(java.security.Permission) checkPermission}
     * method with a {@link RuntimePermission RuntimePermission}{@code
     * ("getClassLoader")} permission to verify that retrieval of the context
     * class loader is permitted.
     *
     * @return  the context ClassLoader for this Thread, or {@code null}
     *          indicating the system class loader (or, failing that, the
     *          bootstrap class loader)
     *
     * @throws  SecurityException
     *          if the current thread cannot get the context ClassLoader
     *
     * @since 1.2
     */
    @CallerSensitive
    public ClassLoader getContextClassLoader() {
        return contextClassLoader;
    }

    /**
     * Sets the context ClassLoader for this Thread. The context
     * ClassLoader can be set when a thread is created, and allows
     * the creator of the thread to provide the appropriate class loader,
     * through {@code getContextClassLoader}, to code running in the thread
     * when loading classes and resources.
     *
     * <p>If a security manager is present, its {@link
     * SecurityManager#checkPermission(java.security.Permission) checkPermission}
     * method is invoked with a {@link RuntimePermission RuntimePermission}{@code
     * ("setContextClassLoader")} permission to see if setting the context
     * ClassLoader is permitted.
     *
     * @param  cl
     *         the context ClassLoader for this Thread, or null  indicating the
     *         system class loader (or, failing that, the bootstrap class loader)
     *
     * @throws  SecurityException
     *          if the current thread cannot set the context ClassLoader
     *
     * @since 1.2
     */
    public void setContextClassLoader(ClassLoader cl) {
        contextClassLoader = cl;
    }

线程上下文加载器,通过源码注释可以看出,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。

每个Thread都有一个ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader。

Android中的ClassLoader

Android中类加载器和Java类加载器差不多,也是遵循双亲委派,只不过JVM加载的是class,Android加载的是dex文件。Android中常用的类加载器如下:

graph LR
A(ClassLoader)-->B(BootClassLoader)
A-->C(BaseDexClassLoader)
A-->D(SecureClassLoader)
C-->E(PathClassLoader)
C-->F(DexClassLoader)
C-->G(InMemoryDexClassLoader)
E-->H(DelegateLastClassLoader)
D-->I(URLClassLoader)

BootClassLoader

用来加载Android Framework层dex文件,他是ClassLoader的内部类,由java代码实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,开发者不可用,在zygote启动的时候创建。
java中根加载器是C++写的,开发者看不到,Android的BootClassLoader是可见的

BaseDexClassLoader

是Path、Dex、InmemoryDex加载器的父类,在构造的时候传入不同的参数,传入参数后,交给DexPathList处理,自身并没有处理。

public class BaseDexClassLoader extends ClassLoader {

    /**
     * Hook for customizing how dex files loads are reported.
     *
     * This enables the framework to monitor the use of dex files. The
     * goal is to simplify the mechanism for optimizing foreign dex files and
     * enable further optimizations of secondary dex files.
     *
     * The reporting happens only when new instances of BaseDexClassLoader
     * are constructed and will be active only after this field is set with
     * {@link BaseDexClassLoader#setReporter}.
     */
    /* @NonNull */ private static volatile Reporter reporter = null;

    private final DexPathList pathList;

    /**
     * Constructs an instance.
     * Note that all the *.jar and *.apk files from {@code dexPath} might be
     * first extracted in-memory before the code is loaded. This can be avoided
     * by passing raw dex files (*.dex) in the {@code dexPath}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android.
     * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
     * @param librarySearchPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }

BaseDexClassLoader构造参数:
1.dexPath:dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’

2.optimizedDirectory:解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:/data/data//…

3.librarySearchPath:包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null

4.parent:父加载器。

PathClassLoader

是DelegateLastClassLoader的父类,
类似Java的AppClassLoader,用于加载系统类和已经安装到系统中的apk里的dex文件(/data/app目录),在zygote启动system_server进程的时候创建。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("TAG", this.getClass().getClassLoader().toString());
        Log.e("TAG1", this.getClass().getClassLoader().getParent().toString());
        <!--Log.e("TAG2", this.getClass().getClassLoader().getParent().getParent().toString());-->
    }
    
    <!--在log中输出如下-->
    
    //E/TAG: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/oa.zjl.com.myapplication-1/base.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]]
    //E/TAG1: java.lang.BootClassLoader@3fed1170
    //E/TAG2:null error

看到Classloader是PathClassLoader,PathClassLoader的parent是BootClassLoader,BootClassLoader的父加载器是null,(因为BootClassLoader是ClassLoader的内部类,无法访问),DexPathList是包含dex的jar或apk包路径,中间用“:”隔开。

DexClassLoader

类似Java中自定义加载器,用于加载指定目录中的dex文件,一般在做插件化或者热更新的时候使用这个加载器

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

对比PathClassLoader和DexClassLoader可以看到构造方法的参数不同。
PathClassloader只能指定dexpath和父加载器,而DexClassloader可以多指定一个librarySearchPath,这就决定了DexClassLoader比PathClassLoader更灵活的加载指定路径下的apk,jar,dex文件,在自定义ClassLoader的使用上更方便了。

DexPathList

final class DexPathList {
    // 声明dex文件后缀名
    private static final String DEX_SUFFIX = ".dex";
    //分隔符
    private static final String zipSeparator = "!/";

    /** 传入的父加载器 */
    private final ClassLoader definingContext;

    /**
     * Element内部封装了 dex/resource/native library 路径
     * 这个是用来存放dex文件路径的数组
     */
    private Element[] dexElements;

    /** 这个是用来存放动态库路径的数组 */
    private final Element[] nativeLibraryPathElements;

    /** app动态库文件列表. */
    private final List<File> nativeLibraryDirectories;

    /** 系统动态库文件列表. */
    private final List<File> systemNativeLibraryDirectories;

    /** 各种IO异常数组. */
    private IOException[] dexElementsSuppressedExceptions;


    public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {

        //如果父加载器为null,那么抛出异常
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
        //如果dex文件路径为null 抛出异常
        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }
        //如果将dex文件优化后放置的文件目录不为空
        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {//且这个目录不存在,那就抛出异常,
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }

            //如果这个要加载的目标目录不能读写,抛出异常
            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }

        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        
        // 通过makeDexElements方法将dex文件路径保存到数组
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);

        //通过splitPaths方法保存应用动态库路径到list
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        
        //通过splitPaths方法保存系统动态库到list
        this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
        
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        //合并两个数组
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
        //将所有的动态库路径保存到数组
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
                                                          suppressedExceptions,
                                                          definingContext);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }
    
    ......
}

加载dex方法调用如下:

BaseDexClassLoader在加载dex文件的findclass方法如下,可以看到findclass方法交到了pathlist处理,如果返回null则爆出我们上面提到的classnotfoundexception

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

在DexPathList中,调用了Element.findclass方法

 public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

Element是DexPathList的内部类,在这里调用DexFile.loadClassBinarayName()

    public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }

在DexFile中调用loadClassBinaryName,最终调用了defineClass中的define、ClassNative方法

 public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }

InMemoryDexClassLoader

用来加载内存中的dex文件。
在构造函数中传入bytebuffer,然后交给BaseDexClassloader

public final class InMemoryDexClassLoader extends BaseDexClassLoader {
    /**
     * Create an in-memory DEX class loader with the given dex buffers.
     *
     * @param dexBuffers array of buffers containing DEX files between
     *                       <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
     * @param parent the parent class loader for delegation.
     */
    public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
        super(dexBuffers, parent);
    }

    /**
     * Creates a new in-memory DEX class loader.
     *
     * @param dexBuffer buffer containing DEX file contents between
     *                       <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
     * @param parent the parent class loader for delegation.
     */
    public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
        this(new ByteBuffer[] { dexBuffer }, parent);
    }
}

,在BaseDex的构造函数中new了个DexPathlist,然后跟上面不同的是调用的是makeInmemoryElement

  private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
            List<IOException> suppressedExceptions) {
        Element[] elements = new Element[dexFiles.length];
        int elementPos = 0;
        for (ByteBuffer buf : dexFiles) {
            try {
                DexFile dex = new DexFile(buf);
                elements[elementPos++] = new Element(dex);
            } catch (IOException suppressed) {
                System.logE("Unable to load dex file: " + buf, suppressed);
                suppressedExceptions.add(suppressed);
            }
        }
        if (elementPos != elements.length) {
            elements = Arrays.copyOf(elements, elementPos);
        }
        return elements;
    }

在new DexFile的时候会调用openinmemorydexfile

 DexFile(ByteBuffer buf) throws IOException {
        mCookie = openInMemoryDexFile(buf);
        mInternalCookie = mCookie;
        mFileName = null;
    }

SecureClassLoader

是URLClassLoader的父类,扩展了ClassLoader的权限安全。

URLClassLoader

通过URL来加载jar文件或文件夹下的类或资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值