ClassLoader

上一篇文章理解虚拟机,知道class文件是通过ClassLoader类加载器加载到JVM内存中的。现在来详细的了解一下Class Loader

Java中的ClassLoader

Bootstrap ClassLoader: 根ClassLoader,用C++实现,专门用来加载Java的核心API:$JAVA_HOME中jre/lib/rt.jar中所有class文件rt的意思是runtime

Extension ClassLoader: 加载Java扩展API jre/lib/ext中的类

App ClassLoader: 加载classpath目录下定义的class,也就是应用程序用到的ClassLoader。加载当前应用程序ClassPath下面的所有的jar和Class文件。

Custom ClassLoader: 可以自定义的ClassLoader,可以继承这个ClassLoader然后自己实现。

Android中的ClassLoader

BootClassLoader
主要用来加载FrameWork层的class文件

PathClassLoader
继承自BaseDexClassLoader,用来加载已经安装到系统中的apk中的class文件,

DexClassLoader
继承自BaseDexClassLoader,用来加载指定的目录中的class文件

ClassLoader的特点

使用双亲代理模型

双亲代理模型定义:当加载一个类的时候,首先查看当前ClassLoader有没有加载过**(加载过的意思就是以前加载过一次放到缓存里了,这里就是查看缓存里有没有)**,如果加载过了直接返回,如果没有加载过,就去查看它的父类有没有加载过此类,如果加载过就返回,一层一层的递归直到顶层。如果还没有找到,就让当前Class Loader执行真正的加载过程

双亲代理的优点:

如果我们的一个类被类加载器树中的任意一个ClassLoader加载过,那么整个App生命周期中都不会再重亲加载这个类了,提高了类加载的效率。比如初始化应用的时候,Framework中的类,被顶层ClassLoader加载过后,后面都不用重新加载这些类了,直接使用。

系统类被提前加载完成,也就提高了系统的安全性。为什么这么说呢,比如java.lang.String类,在应用初始化的时候就加载好了,如果自定义一个String类修改一些病毒函数然后通过自定义的类加载器加载到JVM中,如果没有双亲代理模式,JVM可能认为这个类是系统类,导致执病毒被执行。而现在String类已经被顶级ClassLoader加载过了,自定义的ClassLoader就不会重新加载了。

怎么判断两个类是同一个类呢?

包名相同,类名相同,并且是同一个ClassLoader加载的才是同一个类。

下面看一下ClassLoader的源码,最重要的就是loadClass这个类啦,这里面就实现了双亲委托模式,那就从这里开始

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 第一步
            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;
    }

第一步:findLoadedClass(name)检查这个类是否被加载过,最终执行到native方法去查找
第二步:如果没有并且其父类不为空,执行父类的loadClass()方法
第三步:如果最终还是没有找到,就执行findClass(name)方法自己去加载这个类。

进入findClass(name)方法可以看到

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

这个方法是一个空实现,ClassLoader类是一个abstract抽象类,那个这个方法肯定是让其子类去实现了,去其子类BaseDexClassLoader中看一下。这个类使用android studio看不到,可以去源码网站上看
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/这里面有需要查看的ClassLoader类的源码

public class BaseDexClassLoader extends ClassLoader {

   ......

    @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;
    }

 ......
 

看BaseDexClassLoader中的findClass(String name)方法,可以看到这里调用了pathList.findClass,pathList是BaseDexClassLoader的一个成员变量DexPathList,是在BaseDexClassLoader构造方法中new出来的。

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
           String librarySearchPath, ClassLoader parent, boolean isTrusted) {
       super(parent);
       this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
       if (reporter != null) {
           reportClassLoaderChain();
       }
   }

dexPth:jar或者apk包的路径
optimizedDirectory:最佳文件夹,可复制的文件夹 api26以上已经被弃用
librarySearchPath:native libraries 的路径
parent:父ClassLoader
isTrusted:是否受信任

现在进入DexPathList中的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数组,依次执行element的findClass方法,最终返回Class对象。这个Element数组是DexPathList初始化的时候创建的

  public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
       if (definingContext == null) {
           throw new NullPointerException("definingContext == null");
       }
       if (dexFiles == null) {
           throw new NullPointerException("dexFiles == null");
       }
       if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
           throw new NullPointerException("dexFiles contains a null Buffer!");
       }

        this.definingContext = definingContext;
        // TODO It might be useful to let in-memory dex-paths have native libraries.
        this.nativeLibraryDirectories = Collections.emptyList();
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories);

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                    suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

通过makeInMemoryDexElements这个方法创建

    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;
    }

遍历dexFiles,依次创建DexFile和Element,然后放到数组中返回。看element的findClass方法

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

这里调用的是dexFile的的loadClassBinaryName方法,加载class的二进制name

  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;
    }
 private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
                                                  DexFile dexFile)
            throws ClassNotFoundException, NoClassDefFoundError;

最终追到defineClassNative这个native方法,再往下就是使用C或C++写的定义class的方法了。
回顾一下加载流程:ClassLoader的loadClass方法==>BaseDexClassLoader的findClass方法==>DexPathList的findClass方法==>DexFile的loadClassBinaryName方法。

BaseDexClassLoader有几个子类PathClassLoader,DexClassLoader,InMemoryDexClassLoader,它们的实现都很简单,只有构造方法,具体的实现都是在BaseDexClassLoader中实现。

public class PathClassLoaderextends 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);
    }
}
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
    public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
        super(dexBuffers, parent);
    }
    }
}

DexClassLoader比PathClassLoader多一个optimizedDirectory(指定文件夹必须是内部路径一般是/data/data//…)的参数。

PathClassLoader主要用来加载已经安装到系统的dex文件,没有optimizedDirectory参数因为它的optimizedDirectory默认是/data/dalvik-cache,DexClassLoader可以用来加载指定文件夹下面的dex文件。

InMemoryDexClassLoader主要用于加载内存中的dex文件

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值