Android 虚拟机与类加载机制

1.java虚拟机跟android虚拟机的区别
  • 从执行文件的角度来看:Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。Dalvik虚拟机是在Java虚拟机的基础上做了优化,区别是Android虚拟机执行的Dex文件,java虚拟机执行的是class文件。

  • 从指令集的角度来看:Dalvik虚拟机与Java虚拟机共享有差不多的特性,差别在于两者执行的指令集是不一样的,前者的指令集是基本寄存器的,而后者的指令集是基于堆栈的。

2.基于栈与基于寄存器
  • 对于基于栈的虚拟机来说,每一个运行时的线程都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈桢。最顶部的栈桢称作当前栈桢(局部变量、操作栈数、动态连接、返回地址),其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。
  • 寄存器是CPU的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。
  • 与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了。
3.Dalvik虚拟机与ART虚拟机
  • Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化(Just In Time 即时编译)。
  • ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。
  • Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。
  • ART虚拟机在应用程序安装的时候,在安装时ART虚拟机使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码,这样会增加一些安装的耗时。这就是预先编译机制(Ahead Of Time)

从Android N(7.x)开始,采用混编的方式,在安装的时候,不在进行预编译成机器码了,而是采用JIT和解释的方式。经常JIT的的内容会记录在profile配置当中,当闲时或者充电的时候会将profile中配置的进行机器码编译,再时候的时候直接使用机器码。这样就在减少安装耗时和编译机器码找到了平衡的方案。

4.Android类加载机制

类图

image-20220523144245693

PathClassLoader系统源码:

public class PathClassLoader extends BaseDexClassLoader {
/**
 *
 * 创建一个对给定文件和目录列表进行操作的 {@code PathClassLoader}。
 * 此方法等效于使用第二个参数的 {@code null} 值调用 {@link PathClassLoader(String, String, ClassLoader)}(参见那里的描述)。
 *
 */
public PathClassLoader(String dexPath, ClassLoader parent) {
    super(dexPath, null, null, parent);
}

/**
 * @param dexPath 
 * 包含类和资源的 Jarapk 文件列表,由 {@code File.pathSeparator} 分隔,
 * 在 Android 上默认为 {@code ":"}
 * 
 * @param librarySearchPath 包含本机库的目录列表,由 {@code File.pathSeparator} 分隔;
 *                          可能是 {@code null}
 *                          
 * @param parent the parent class loader  父类加载器
 */
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
    super(dexPath, null, librarySearchPath, parent);
}
}

继承BaseDexClassLoader,只有提供了两个构造方法供使用

DexClassLoader系统源码:

public class DexClassLoader extends BaseDexClassLoader {
    
    /**
     *
     * 创建一个 {@code DexClassLoader} 来查找解释代码和本机代码。
     * 解释类可以在 Jar 或 APK 文件中包含的一组 DEX 文件中找到。
     *
     *      包含类和资源的 Jarapk 文件列表,由 {@code File.pathSeparator} 分隔,
     *                在 Android 上默认为 {@code ":"}
     *
     * @param optimizedDirectory 
     *       此参数已弃用,自 API 级别 26 起无效。
     *
     *     包含本机库的目录列表,由 {@code File.pathSeparator} 分隔;可能是 {@code null}
     *
     * @param parent the parent class loader
     *               parent 父类加载器
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}
  • dexPath:Dex路径如果是多个可以使用:符号分割
  • 继承BaseDexClassLoader ,只提供一个构造方法供使用

由于只提供了构造方法,那么类加载的方法在父类

ClassLoader->loadClass系统源码:

 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.
//                    如果仍未找到,则调用 findClass 以查找类。
                    c = findClass(name);
                }
            }
            return c;
    }
  • 首先检查已经加载的类里边是否已经包含了该类,如果存在直接返回

  • 某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。

这就是双亲委托机制,这么做的好处:

  • 避免类被重复加载
  • 安全,系统已经被加载的类,不会被重复加载,防止被篡改。

BaseDexClassLoader->findClass系统源码:

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

这个方法调用了pathList.findClass ,从类图可以看到,BaseDexClassLoader包含一个成员变量:DexPathList pathList

DexPathList->findClass系统源码:

/**
 *
 * 在此实例指向的 dex 文件之一中查找命名类。这将在最早列出的路径元素中找到一个。
 * 如果类已找到但尚未定义,则此方法将在构造此实例的定义上下文中定义它。
 */
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;
}

通过遍历dexElements,拿到Element,每一个Element对应一个Dex文件,调用了Element的findClass方法

Element->findClass系统源码:

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

Element是DexPathList的一个内部类,持有File path以及DexFile dexFile,dexFile就是native的laod方法了

通过分析源码可以得到结论:关键点是通过遍历dex的数组来加载class的,那么来看下Element数组是如何初始化的:

  DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }

        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }

        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>();
        // save dexPath for BaseDexClassLoader
        //splitDexPath(dexPath) :根据外部传入的dex地址 解析成element数组
        // 解析dex路径 将多个解析成一个数组
//        this.dexElements  dex节点数组 一个dexcuing
		//这里给dexElements 数组初始化的
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);


//        本机库可能同时存在于系统和应用程序库路径中,我们使用以下搜索顺序:

//        1.这个类加载器的应用程序库的库路径(librarySearchPath):
//        1.1。本机库目录 1.2。 apk 文件中库的路径 2. 来自系统库的系统属性的
//        VM 的库路径,也称为 java.library.path
        //
        // This order was reversed prior to Gingerbread; see http://b/2933456.
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

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

在DexPathlist的构造方法,通过makeDexElements方法来初始化dexElements数组的,第一个参数:splitDexPath(dexPath)是对传入的Dex路径进行切割来处理的,多个dex路径可以使用:来分割出路径数组。

类加载的顺序:ClassLoader.loadCalss()->findLoadedClass(本地查找)->parent.loadClass()先从父类找->findClass()没找到再通过findClass找->pathList.findClass 其实就是从DexPathList 的findClass找->element.findClass 遍历dexElements遍历得到elment执行Element.findClass->dexFile.loadClassBinaryName Element里边有成员变量dexFile,然后再通过DexFile去找->dexFile.defineClassNative 最终通过native去找。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值