理解Java ClassLoader & Android ClassLoader

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013762572/article/details/84026834

1.Java中的ClassLoader

分为两种类型:系统加载器和自定义类加载器;其中系统加载器包括三种:BootStrap ClassLoader、Extensions ClassLoader和Application ClassLoader。其分别的作用是:

1.1 BootStrap ClassLoader(引导类加载器)

使用C/C++实现的加载器,用于加载指定的JDK核心类库,像java.lang;java.reflect;java.utils这一类系统类。它用它来加载一下路径的类库:

$JAVA_HOME/jre/lib目录下
-Xbootclasspath参数指定下的目录

java虚拟机的启动时就是通过BootStrap ClassLoader来创建一个初始类来完成的(类似main函数)。由于BootStrap ClassLoader是使用C/C++实现的,所以不能为Java代码访问到。
BootStrap ClassLoader并不没有继承java.lang.ClassLoader。

1.2 Extensions ClassLoader(扩展类加载器)

java语言中表现为ExtClassLoader,用于加载Java的扩展类。在Java9之后,更名为PlatformClassLoader。它提供了除了系统类之外的额外功能,它主要用来加载一下目录下的类库:

$JAVA_HOME/jre/lib/ext目录下
系统属性java.ext.dir所指定的目录

1.3 Application ClassLoader(应用程序加载器)

java语言中实现为AppClassLoader,用于加载一下路径的类库

当前程序的Classpath目录
系统属性java.class.path所指定的目录

2. 双亲委派模式

java类加载器查找Class文件时,就是采用双亲委派模式。基本套路为:

1.先查找该class文件时是否加载过,如果加载过,直接返回
2.没有加载过,先查找class的任务委派给自己的父加载器
3.如果还没有找到,就再委派给自己的父类加载器

4. 最后将抛给顶层的BootStrap ClassLoader,如果BC找到了就返回;否则就按原路返回,叫子类加载器去加载目标class,如果没有找到,在叫子子类加载器去加载
5.最终找到就返回,否则抛出异常

画图显示大致过程:
在这里插入图片描述

对于我们需要加载的路劲下的class文件,此时该class文件不在当前程序目录中,此时我们就需要用到了自定义classLoader了。自定义的classLoader需要继承java.lang.ClassLoader,并复写loadClass方法。一般寻找class文件的套路如下:

  1. 自定义classLoader会在自己的缓存中寻找该class文件是否被加载过,如果有直接返回;如果没有,则将任务委托给我们的AppClassLoader;
  2. 按照上图的方式进行递归查找;
  3. 一直委托到BootStrap ClassLoader,如果BS的缓存中不存在该class文件,则在$JAVA_HOME/jre/lib或者 -Xbootclasspath参数指定下的目录 中查找,如果找到就直接返回Class,没有则交给子加载器ExtClassLoader查找;
  4. 同理,ExtClassLoader加载机制到AppClassLoader最后到自定义的CustomClassLoader,最终没有找到就跑出异常。

2.1 双亲委派的缘由

java的创建者们为什么会花这么大力气来实现这个类的加载机制呢?
主要是为了安全起见!!!如果不使用这种双亲委派模式,我们也可以自定义一个java.lang.String类来代替系统的java.lang.String类,这样很显然会造成不可预测的安全隐患,采用双亲委派模式可以使得系统的String类在VM启动被优先加载,这样我们自定义的String类将不能代替系统的String类。

ps: java虚拟机认为两个类是同一个类需要满足两个条件:

  1. 两个类名一致[packagename + classname]
  2. 被同一个类加载器加载

另外双亲委派还有一个好处就是可以避免类被重复加载,如果该类已经加载过,就没有必要重复加载一次,直接读取缓存即可。

3. 自定义classLoader

只需要继承ClassLoader,并重写findClass即可。自定义的ClassLoader只能过加载指定目录下的jar包或者Class文件,所以我们需要指定所在的目录即可,下面就是一个例子:

import java.io.*;

public class MyClassLoader extends ClassLoader{
    //定义指定的目录
    private String path;

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass,name:" + name);

        byte[] bytes = loadClassByte(name);
        if(null == bytes) {
            throw new ClassNotFoundException(name + " class not found");
        }
		
		//直接调用父类定义的natvive方法加载指定的Class类
        return defineClass(name,bytes,0,bytes.length);
    }

	//将目下下的class文件转换为二进制文件
    private byte[] loadClassByte(String name){
        String fileName = getFileName(name);

        File file = new File(path, fileName);
        InputStream inputStream = null ;
        ByteArrayOutputStream outputStream = null ;

        byte[] bytes = new byte[1024 * 4];
        int length = -1;
        try {
            inputStream = new FileInputStream(file);
            outputStream = new ByteArrayOutputStream();

            while ((length = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes,0,length);
            }

            return  outputStream.toByteArray();

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            closeQuite(inputStream);
            closeQuite(outputStream);
        }

        return null ;
    }

    private String getFileName(String name) {
        int index = name.lastIndexOf(".");
        if(index == -1) {
            return name.concat(".class");
        }
        return name.substring(index + 1) .concat(".class");
    }

    private void closeQuite(Closeable closeable){
        if(null != closeable ) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

然后我写个测试Entity:

package com.xing;

public class Person {

    private String name ;
    public int age ;

    public Person(String name , int age) {
        this.name = name;
        this.age = age ;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

我们再来写个测试:

    public static void main(String[] args) throws Exception {
        //目录自己定义
        MyClassLoader classLoader = new MyClassLoader("D:\\java\\idea\\work\\out\\production\\work\\com\\xing");
        Class clazz = classLoader.findClass("com.xing.Person");

        Object object = clazz.getDeclaredConstructor(String.class, int.class).newInstance("zhangsan", 18);

        System.out.println(object);
        
        System.out.println(Person.class.getClassLoader());
        System.out.println(object.getClass().getClassLoader());
        System.out.println(object instanceof Person);
    }

我们看一下输出:

findClass,name:com.xing.Person
Person{name='zhangsan', age=18}
jdk.internal.loader.ClassLoaders$AppClassLoader@726f3b58
com.xing.MyClassLoader@58ceff1
false

看最后两行,我们可以看出使用自定义的ClassLoader可以加载指定的Class文件,最后一行可以看出object instanceOf Person显示结果是false,即二者不是同一个类。因为两个类的classloader是不一致的,所以说明判断两个类是否一致不仅需要看类名,还需要看两者的类加载器是否一致。

4. Andorid中的ClassLoader

4.1 在Android中ClassLoader的类型

和Java中不一致,Java虚拟机中加载的是class文件,而Android中加载的是dex文件。二者是有区别的。Android中classLoader也分为两种类型:系统加载器和自定义加载器。其中系统加载器分为BootClassLoader、PathClassLoader和DexClassLoader。

4.2 BootClassLoader

Android系统启动时会使用BootClassLoader来加载常用类,与Java的BootStrap ClassLoader不同,BootClassLoader使用的是java编写的:

### /libcore/ojluni/src/main/java/java/lang/ClassLoader.java
1336 class BootClassLoader extends ClassLoader {
1337
1338    private static BootClassLoader instance;
1339
1340    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
1341    public static synchronized BootClassLoader getInstance() {
1342        if (instance == null) {
1343            instance = new BootClassLoader();
1344        }
1345
1346        return instance;
1347    }

BootClassLoader是ClassLoader的内部类,并继承了ClassLoader.BootClassLoader是一个单例类。而且BootClassLoader的修饰符是默认的,只能在同一个包可以访问,因此在外部是不能调用的。

4.3 DexClassLoader

DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk和jar文件),DexClassLoader的代码如下:

####  /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
36public class DexClassLoader extends BaseDexClassLoader {
37    /**
38     * Creates a {@code DexClassLoader} that finds interpreted and native
39     * code.  Interpreted classes are found in a set of DEX files contained
40     * in Jar or APK files.
41     *
42     * <p>The path lists are separated using the character specified by the
43     * {@code path.separator} system property, which defaults to {@code :}.
44     *
45     * @param dexPath the list of jar/apk files containing classes and
46     *     resources, delimited by {@code File.pathSeparator}, which
47     *     defaults to {@code ":"} on Android
48     * @param optimizedDirectory directory where optimized dex files
49     *     should be written; must not be {@code null}
50     * @param librarySearchPath the list of directories containing native
51     *     libraries, delimited by {@code File.pathSeparator}; may be
52     *     {@code null}
53     * @param parent the parent class loader
54     */
55    public DexClassLoader(String dexPath, String optimizedDirectory,
56            String librarySearchPath, ClassLoader parent) {
57        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
58    }
59}

参数为:

  1. dexPath jar/apk文件路径的集合,默认分割符为:
    2.optimizedDirectory 解压dex文件的路径
    3.librarySearchPath 本地文件库(c/c++文件)
    parent 父classLoader

4.4 PathClassLoader

加载系统类和应用程序类

#### /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
25 public class PathClassLoader extends BaseDexClassLoader {
26    /**
27     * Creates a {@code PathClassLoader} that operates on a given list of files
28     * and directories. This method is equivalent to calling
29     * {@link #PathClassLoader(String, String, ClassLoader)} with a
30     * {@code null} value for the second argument (see description there).
31     *
32     * @param dexPath the list of jar/apk files containing classes and
33     * resources, delimited by {@code File.pathSeparator}, which
34     * defaults to {@code ":"} on Android
35     * @param parent the parent class loader
36     */
37    public PathClassLoader(String dexPath, ClassLoader parent) {
38        super(dexPath, null, null, parent);
39    }
40
41    /**
42     * Creates a {@code PathClassLoader} that operates on two given
43     * lists of files and directories. The entries of the first list
44     * should be one of the following:
45     *
46     * <ul>
47     * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
48     * well as arbitrary resources.
49     * <li>Raw ".dex" files (not inside a zip file).
50     * </ul>
51     *
52     * The entries of the second list should be directories containing
53     * native library files.
54     *
55     * @param dexPath the list of jar/apk files containing classes and
56     * resources, delimited by {@code File.pathSeparator}, which
57     * defaults to {@code ":"} on Android
58     * @param librarySearchPath the list of directories containing native
59     * libraries, delimited by {@code File.pathSeparator}; may be
60     * {@code null}
61     * @param parent the parent class loader
62     */
63    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
64        super(dexPath, null, librarySearchPath, parent);
65    }
66}

5. ClassLoader的继承关系

在Andorid代码中,打印类加载器:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ClassLoader loader = MainActivity.this.getClassLoader();
        while (null != loader) {
            Log.d("TAG" , "--->>" + loader);
            loader = loader.getParent();
        }
    }

打印的结果为:

dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/base.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_dependencies_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_resources_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_0_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_1_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_2_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_3_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_4_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_5_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_6_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_7_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_8_apk.apk", 
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_9_apk.apk"],
nativeLibraryDirectories=[/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/lib/x86, /system/lib, /vendor/lib]]]

 D/TAG: --->>java.lang.BootClassLoader@cc90c73

可以看出存在两种类加载器,一种是PathClassLoader,另外一种是BootClassLoader. dexPathList中包含了很多apk的路径,其中/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/base.apk

6. Android中双亲委派过程

同样需要查看ClassLoader的findClass方法:

/** <@link> 
http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java#180
*/
359    protected Class<?> loadClass(String name, boolean resolve)
360        throws ClassNotFoundException
361    {
362            // First, check if the class has already been loaded
363            Class<?> c = findLoadedClass(name);
364            if (c == null) {
365                try {
366                    if (parent != null) {
367                        c = parent.loadClass(name, false);
368                    } else {
369                        c = findBootstrapClassOrNull(name);  //return null at the time
370                    }
371                } catch (ClassNotFoundException e) {
372                    // ClassNotFoundException thrown if class not found
373                    // from the non-null parent class loader
374                }
375
376                if (c == null) {
377                    // If still not found, then invoke findClass in order
378                    // to find the class.
379                    c = findClass(name);
380                }
381            }
382            return c;
383    }

大致过程上面注释已经很清楚了,如果没有找到,就委托给父类加载器;如果都没有,则执行findClass,其中findClass中:

404    protected Class<?> findClass(String name) throws ClassNotFoundException {
405        throw new ClassNotFoundException(name);
406    }

看来需要子类来实现了,我们看一下它的子类BaseDexClassLoader的findClass方法:

### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java#30

88    @Override
89    protected Class<?> findClass(String name) throws ClassNotFoundException {
90        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
91        Class c = pathList.findClass(name, suppressedExceptions);
92        if (c == null) {
93            ClassNotFoundException cnfe = new ClassNotFoundException(
94                    "Didn't find class \"" + name + "\" on path: " + pathList);
95            for (Throwable t : suppressedExceptions) {
96                cnfe.addSuppressed(t);
97            }
98            throw cnfe;
99        }
100        return c;
101    }

可以看出是由pathList中获取的,那么这个pathList是从哪里来的呢?是从构造方法里面传入的:

 ### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java#30
 
82    public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
83        // TODO We should support giving this a library search path maybe.
84        super(parent);
85        this.pathList = new DexPathList(this, dexFiles);
86    }

然后我们看一下DexPathList中findClass方法:

### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java#86

464    public Class<?> findClass(String name, List<Throwable> suppressed) {
465        for (Element element : dexElements) {
466            Class<?> clazz = element.findClass(name, definingContext, suppressed);
467            if (clazz != null) {
468                return clazz;
469            }
470        }
471
472        if (dexElementsSuppressedExceptions != null) {
473            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
474        }
475        return null;
476    }
477

然后再看Element中findClass的方法:

    /*package*/ static class Element {
675        public Class<?> findClass(String name, ClassLoader definingContext,
676                List<Throwable> suppressed) {
677            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
678                    : null;
679        }

这次是dexFile来loadClassBinaryName了,我们查询一下这个DexFile:

### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#42

274    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
275        return defineClass(name, loader, mCookie, this, suppressed);
276    }
277
278    private static Class defineClass(String name, ClassLoader loader, Object cookie,
279                                     DexFile dexFile, List<Throwable> suppressed) {
280        Class result = null;
281        try {
282            result = defineClassNative(name, loader, cookie, dexFile);
283        } catch (NoClassDefFoundError e) {
284            if (suppressed != null) {
285                suppressed.add(e);
286            }
287        } catch (ClassNotFoundException e) {
288            if (suppressed != null) {
289                suppressed.add(e);
290            }
291        }
292        return result;
293    }

到了最后,需要调用defineClassNative本地方法查找相关的Class文件。
ClassLoader的加载过程就是遵循了双亲委派模式,如果委托流程没有检查到此前传入的类,就调用ClassLoader的findClass方法,Java层最终会调用DexFile的defineClassNative来执行查找流程。

7.总结

基本如下:

1.Java的顶级类加载器BootStrap ClassLoader是有C/C++编写,Java层不能访问到;
2.Android的顶级类加载器BootClassLoader是由Java语言编写,Java层可以访问;Android类的加载器继承关系比java中复杂一些,提供的功能比较多。
3. java和Android的类加载器加载的文件不一致,前者是class文件,或者是dex文件;正因为如此,Android中没有了ExtClassLoader和AppClassLoader,取代他们的是PathClassLoader和DexClassLoader。

学习之【刘望舒—>[Andorid进阶解密] https://item.jd.com/28229567338.html】

猜你喜欢

转载自blog.csdn.net/u013762572/article/details/84026834
今日推荐