栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android 虚拟机与类加载机制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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类加载机制

类图

PathClassLoader系统源码:

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

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

DexClassLoader系统源码:

public class DexClassLoader extends BaseDexClassLoader {
    
    
    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 suppressedExceptions = new ArrayList();
    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系统源码:

public Class findClass(String name, List 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 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 suppressedExceptions = new ArrayList();
        // 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 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去找。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/926833.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号