【Android食用指南-01】Android虚拟机&类加载机制

代码 代码 1285 人阅读 | 0 人回复

<
媒介

正在上一篇文章中【JVM进门食用指北-03】JVM渣滓采取器和机能调劣[1],对渣滓采取器JVM机能调劣停止了叙说,并对JVM中CMS渣滓采取器停止了重面论述。经由过程之前正在JVM中的展垫,本文我们回到Android中,本文我们次要对 Android假造机类减载机造停止叙说,减深对 Android 的了解。
行将教会



  • ART 战 Dalvik
  • Android 类减载流程
  • 热建复完成道理
ART 取 Dalvik

JVM 取 DVM

JVM 是我们的 Java 假造机,而我们的Android使用运转正在 Dalvik/ART 假造机上的。每个使用对应一个 Dalvik 假造机,固然 Dalvik 假造机也算 Java 假造机,不外施行的没有是.class文件,而是dex文件。
155306xwddvnxf1dr9d7r9.png

那里对上述名词停止一些叙说
假造机分类

基于栈的假造机 每个运转的线程,皆有一个自力的栈,栈中记载了办法挪用的汗青,每个办法的挪用,皆对应一个栈帧,而且将栈帧压进栈中,最顶部的栈帧为当前栈帧,既 当前施行的办法,基于栈帧 取 操纵数栈停止一切操纵。
存放器 CPU的构成部门,存放器是有限存储容量的下速存储容器(计较机中各级存储速率 存放器-> 一级缓存->两级缓存 -> 内乱存 -> 硬盘),能够用去办理指令、数据的地点。
基于存放器的假造机 (模仿存放器的操纵流程)
基于存放器的假造机中出有操纵数栈,不外有许多假造存放器,相同操纵数栈,那些存放器也寄存正在运转时栈中,素质上便是一个数组,取JVM类似,正在Dalvik 假造机中每一个线程皆有本人的PC 战 挪用栈,办法挪用的举动记载以帧为单元保留正在挪用栈上。
差别假造机流程

我们比照一下 基于栈的流程 战 基于存放器的流程
155306mzttztj6nt69k7tt.png

JVM流程阐发参考之前文章阐发,凡是需求颠末以下操纵


  • 如将栈顶某范例值存进部分变量某处
  • 将部分变量中某处某范例常量压进操纵数栈
  • 施行相干指令
155306hmad8dxnmvmx1inn.png

DVM 中,间接根据指令,将某范例值存进假造存放器中,相干的操纵也根据指令正在假造存放器中经由过程CPU处置,将处置成果返回假造存放器(免却了基于栈的流程中部分变量取操纵数栈中数据的活动
155307xmw17722u4p8l813.png

ART 取 Dalvik 区分

155307ylp7k8k9p89681w8.png

ART 战 Dalvik 皆是基于存放器的假造机
Dalvik 施行 dex字节码注释施行,正在Android2.2 开端,撑持立即编译,既挑选法式中常常施行的代码(又称热门代码),停止编译战劣化 削减了JVM中注释的流程。
ART假造机 (Android4.4) 间接跑机械码没有停止字节码注释。Android的运转时从Dalvik 无影响过渡ART,由于并不用开辟者将使用间接编译成目的机械码,APk照旧是一个包含dex字节码的文件。
为何APK照旧是包含dex字节码的文件,而ART战 Dalvik的施行方法纷歧样?

那里ART间接跑字节码,而Dalvik停止注释施行,并正在2.2引进JIT,可是皆是dex文件。
由于APK文件需求正在Android体系中运转,需求停止装置,那个时分,颠末假造机,会对dex停止劣化,Dalvik中,会将dex字节码停止劣化天生odex文件,而ART 会将 dex字节码翻译为当地机械码(引进了预先编译机造 Ahead of Time),装置时,ART利用装备自带的dex2oat东西对使用停止编译。dex中的字节码被编译成本天机械码,那也是为何我们装置的时分比力缓。
不外前面从Android O 开端,装置速率变快了,由于那个时分接纳混淆编译了,JIT、AOT、战字节码注释。 当使用初次装置时,没有停止AOT编译,运转过程当中接纳注释施行,并将法式中常常要施行的代码停止立即编译(JIT),并将JIT编译后的办法记载到Profile设置文件,当装备忙置时,利用编译保护过程,根据Profile文件对经常使用文件停止AOT编译(天生.art文件),以便下次运转时间接利用。假如下次法式运转,将检测该文件并剖析出机械码中文件中的类疑息,减载到ClassLoader中。
ClassLoader 及 类减载流程

ClassLoader

每个Java法式皆是有一个或多个class文件构成,正在法式运转时,需求将class文件减载到JVM中才可使用,而减载class文件的是Java的类减载机造,ClassLoader减载class文件供给法式运转时利用,每一个Class工具内乱部皆有一个classLoader字段去标识由何种classLoader减载。
ClassLoader 类 和 类构造

155307puugemdeoofewkde.png

155308bey4y4dvzbylilig.png



  • ClassLoader
  • BootClassLoader 用于减载Android Framework 层 Class文件(像String.Class)
  • PathClassLoader 持续自ClassLoader 用于Android使用法式减载类
类减载流程

PathClassLoader

上面我们阐发一下PathClassLoader类去阐发类减载流程
  1. /**
  2. * Loads the class with the specified <a href="#name">binary name</a>.  The
  3. * default implementation of this method searches for classes in the
  4. */
  5. // Android-removed: Remove references to getClassLoadingLock
  6. //                   Remove perf counters.
  7. //
  8. // <p> Unless overridden, this method synchronizes on the result of
  9. // {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
  10. // during the entire class loading process.
  11. //
  12. protected Class<?> loadClass(String name, boolean resolve)
  13.     throws ClassNotFoundException
  14. {      
  15.         //找缓存,曾经减载过的便没有会被减载了
  16.         //class文件需求转换为class工具 耗时操纵
  17.         // First, check if the class has already been loaded
  18.         Class<?> c = findLoadedClass(name);
  19.         //出有缓存进if
  20.         if (c == null) {
  21.             try {
  22.                 //假如该类下的parent工具真例没有为空,操纵该parent工具减载相干类
  23.                 //该工具指背 BootClassLoader 以下图所示
  24.                 if (parent != null) {
  25.                     c = parent.loadClass(name, false);
  26.                 } else {
  27.                     //parent 为空,返回空
  28.                     c = findBootstrapClassOrNull(name);
  29.                 }
  30.             } catch (ClassNotFoundException e) {
  31.                 // ClassNotFoundException thrown if class not found
  32.                 // from the non-null parent class loader
  33.             }
  34.             if (c == null) {
  35.                 // If still not found, then invoke findClass in order
  36.                 // to find the class.
  37.                 //假如 parent(BootstrapClass)中减载没有到该类
  38.                 //则 挪用本身findClass来减载类(PathClassLoader)
  39.                 c = findClass(name);
  40.             }
  41.         }
  42.         //有缓存间接返回
  43.         return c;
  44. }
  45. /**
  46. * Returns a class loaded by the bootstrap class loader;
  47. * or return null if not found.
  48. */
  49. private Class<?> findBootstrapClassOrNull(String name)
  50. {
  51.     return null;
  52. }
复造代码
我们能够经由过程上述代码阐发 战 PathClassLoader类构造图获得该类的loadClass流程
155308om8d2x2a25z9rm11.png

那末为何我们ClassLoader中要停止如许的设想呢 ?
起首,那个parent的目标正在于完成类减载的单亲拜托,起首减载使命拜托给女类减载器,顺次递回,假如女类减载器能够完成类减载使命,则胜利返回,只要女类减载器没法完成此减载使命时,才本人来完成减载。 而完成这类机造,能够到达以下结果


  • 制止反复减载,当parent对应的减载器曾经减载了该类的时分,便出有必有当前classloader再减载一次,间接正在parent中的缓存找。
  • 宁静思索,避免核心API被窜改,像本人界说一个String.java 那个时分classloader会先减载framework中的String.java(减载只需齐限制名一样,能够假造构建战体系类一样的齐限制名)
上里我们阐发了它的设想思惟,回回减载过程当中,那里用到了findClass(name) 办法
findClass(name) 又做了甚么?
我们能够经由过程检察其本身战女类寻觅findClass办法,经由过程检察
  1.    @Override
  2.     protected Class<?> findClass(String name) throws ClassNotFoundException {
  3.         List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
  4.         //经由过程pathList 来找 相干 类,而pathList 是  DexPathList 是 final 的
  5.         Class c = pathList.findClass(name, suppressedExceptions);
  6.         //明白了 返回 Class相干真例工具,出找到扔非常 枢纽正在上一止代码
  7.         if (c == null) {
  8.             ClassNotFoundException cnfe = new ClassNotFoundException(
  9.                     "Didn&#39;t find class "" + name + "" on path: " + pathList);
  10.             for (Throwable t : suppressedExceptions) {
  11.                 cnfe.addSuppressed(t);
  12.             }
  13.             throw cnfe;
  14.         }
  15.         return c;
  16.     }
  17.   //上述pathList 声明 是不成变的 我们来检察其赋值
  18.   private final DexPathList pathList;
  19.      //pathList 又是由甚么赋值 能够看到相干利用正在机关办法中
  20.      // 经由过程检察其机关办法,可知我们需求传进 dexPath 来 真例化 DexPathList
  21.      // 因而PathClassLoader才能够减载到法式的一切类
  22.      /**
  23.      * @hide
  24.      */
  25.     public BaseDexClassLoader(String dexPath, File optimizedDirectory,
  26.             String librarySearchPath, ClassLoader parent, boolean isTrusted) {
  27.         super(parent);
  28.         //我们跳来 DexPathList 看看
  29.         this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
  30.         if (reporter != null) {
  31.             reportClassLoaderChain();
  32.         }
  33.     }
  34. // 我们看一下DexPathList
  35. DexPathList(ClassLoader definingContext, String dexPath,
  36.             String librarySearchPath, File optimizedDirectory, boolean isTrusted) {      
  37.             //通例 参数判空
  38.         if (definingContext == null) {
  39.             throw new NullPointerException("definingContext == null");
  40.         }
  41.         if (dexPath == null) {
  42.             throw new NullPointerException("dexPath == null");
  43.         }
  44.         if (optimizedDirectory != null) {
  45.             if (!optimizedDirectory.exists())  {
  46.                 throw new IllegalArgumentException(
  47.                         "optimizedDirectory doesn&#39;t exist: "
  48.                         + optimizedDirectory);
  49.             }
  50.             if (!(optimizedDirectory.canRead()
  51.                             && optimizedDirectory.canWrite())) {
  52.                 throw new IllegalArgumentException(
  53.                         "optimizedDirectory not readable/writable: "
  54.                         + optimizedDirectory);
  55.             }
  56.         }
  57.         this.definingContext = definingContext;
  58.         ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
  59.         // save dexPath for BaseDexClassLoader
  60.         // 那里利用到了 dexpath
  61.         // splitDexPath 将dexpath的多地点别离 , PathClassLoader 能够处置多个dex
  62.         // makeDexElements 根据dexpath天生多个dexElement
  63.         // 上面我们来看一下splitDexPath 战 makeDexElements
  64.         this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
  65.                                            suppressedExceptions, definingContext, isTrusted);
  66.         this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
  67.         this.systemNativeLibraryDirectories =
  68.                 splitPaths(System.getProperty("java.library.path"), true);
  69.         List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
  70.         allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
  71.         this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
  72.         if (suppressedExceptions.size() > 0) {
  73.             this.dexElementsSuppressedExceptions =
  74.                 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
  75.         } else {
  76.             dexElementsSuppressedExceptions = null;
  77.         }
  78.     }
  79. //splitDexPath 办法
  80.     private static List<File> splitDexPath(String path) {
  81.         return splitPaths(path, false);
  82.     }
  83.     /**
  84.      * Splits the given path strings into file elements using the path
  85.      * separator, combining the results and filtering out elements
  86.      * that don&#39;t exist, aren&#39;t readable, or aren&#39;t either a regular
  87.      * file or a directory (as specified). Either string may be empty
  88.      * or {@code null}, in which case it is ignored. If both strings
  89.      * are empty or {@code null}, or all elements get pruned out, then
  90.      * this returns a zero-element list.
  91.      */
  92.     private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
  93.         List<File> result = new ArrayList<>();
  94.         if (searchPath != null) {
  95.             for (String path : searchPath.split(File.pathSeparator)) {
  96.                 if (directoriesOnly) {
  97.                     try {
  98.                         StructStat sb = Libcore.os.stat(path);
  99.                         if (!S_ISDIR(sb.st_mode)) {
  100.                             continue;
  101.                         }
  102.                     } catch (ErrnoException ignored) {
  103.                         continue;
  104.                     }
  105.                 }
  106.                 result.add(new File(path));
  107.             }
  108.         }
  109.         return result;
  110.     }
  111.     //makePathElements 办法
  112.     private static final String zipSeparator = "!/";
  113.     private static NativeLibraryElement[] makePathElements(List<File> files) {
  114.         // 一个dex 天生一个 elements 工具
  115.         NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
  116.         int elementsPos = 0;
  117.         for (File file : files) {
  118.             String path = file.getPath();
  119.             if (path.contains(zipSeparator)) {
  120.                 String split[] = path.split(zipSeparator, 2);
  121.                 File zip = new File(split[0]);
  122.                 String dir = split[1];
  123.                 elements[elementsPos++] = new NativeLibraryElement(zip, dir);
  124.             } else if (file.isDirectory()) {
  125.                 // We support directories for looking up native libraries.
  126.                 elements[elementsPos++] = new NativeLibraryElement(file);
  127.             }
  128.         }
  129.         if (elementsPos != elements.length) {
  130.             elements = Arrays.copyOf(elements, elementsPos);
  131.         }
  132.         return elements;
  133.     }
复造代码
那里,我们了解到了pathList那个工具为了findClass做了甚么筹办,如今,我们回到BaseClassLoader 中的 findClass 办法中,持续看pathList那一止代码,pathList那个真例我们曾经晓得了,如今看它挪用的findClass办法(战BaseClassLoader中纷歧样)
  1.     //正在那里,我们能够看到 该办法利用到了方才 pathList 中 dexElements数组
  2.     //  经由过程遍历 每个 Element 遍历一切的dex
  3.     //   DexFile dex = element.dexFile; 拿到对应的 DexFile
  4.     //  dex.loadClassBinaryName(name, definingContext, suppressed);
  5.     //  再经由过程每一个dex 的 loadClassBinaryName 来将类找出
  6.   /**
  7.      * Finds the named class in one of the dex files pointed at by
  8.      * this instance. This will find the one in the earliest listed
  9.      * path element. If the class is found but has not yet been
  10.      * defined, then this method will define it in the defining
  11.      * context that this instance was constructed with.
  12.      *
  13.      * @param name of class to find
  14.      * @param suppressed exceptions encountered whilst finding the class
  15.      * @return the named class or {@code null} if the class is not
  16.      * found in any of the dex files
  17.      */
  18.     public Class findClass(String name, List<Throwable> suppressed) {
  19.         for (Element element : dexElements) {
  20.             DexFile dex = element.dexFile;
  21.             if (dex != null) {
  22.                 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
  23.                 if (clazz != null) {
  24.                     return clazz;
  25.                 }
  26.             }
  27.         }
  28.         if (dexElementsSuppressedExceptions != null) {
  29.             suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
  30.         }
  31.         return null;
  32.     }
复造代码
大抵的类减载流程便如上所示
热建回复复兴理

那末,经由过程类减载流程,我们那个时分能够正在那之长进止一下热建复
155308l3911gnkl1931a07.png

当我们法式有Bug时,我们对相干类停止修正,并将修正后的类挨包成补钉dex,当客户端下载时,只需让补钉包减载正在类减载之前,就能够提早减载到假造机中,而有毛病的类,由于类减载机造成绩,没有会反复减载,从而没有会减载到毛病类,进而完成建复。
热建回复复兴理大抵代码流程

  1.         //相干一般建复类挨补钉包
  2.         dx --dex --output=output.dex  xxx.jar
  3.         dx --dex --output=output.dex  类齐限制名
  4.         正在Application类中 重写 attachBaseContext 并正在super后 挪用 热建复
  5.         并传进Application,补钉dex文件至热建复类
  6.         //热建复类中
  7.         //1、得到classloader(PathClassLoader)
  8.         ClassLoader classLoader = application.getClassLoader();
  9.         //2\. 差别的Android 版本 ,热建复有所差别,需求对其适配 (以6.0为例)
  10.         //3\. 遍历该类及超类
  11.         Class<?> clazz = classLoader.getClass(); clazz != null; clazz = clazz.getSuperclass()
  12.         //4.经由过程反射 获得 pathList 属性成员
  13.         Field field = clazz.getDeclaredField("pathList");
  14.         //5.获得到pathList工具
  15.         Object dexPathList = pathListField.get(classLoader);
  16.         ///将本人的补钉包转换为Element数组 参考PathClassLoader
  17.         // 获得补钉包的Element数组
  18.         // 反射拿到 makePathElements 办法
  19.         Class<?> clazz = dexPathList.getClass(); clazz != null; clazz = clazz.getSuperclass()
  20.         Method method = clazz.getDeclaredMethod("makePathElements", parameterTypes);
  21.         //反射施行办法 返回 补钉包对应的 Element数组
  22.          Object[] patchElements = (Object[]) method.invoke(dexPathList, files, optimizedDirectory,
  23.                     suppressedExceptions)
  24.        //将本来的 dexElements 取 makePathElements天生的数组兼并,并操纵反射塞回 dexElements   
  25.        //拿到 classloader中的dexelements 数组
  26.        Class<?> clazz = dexPathList.getClass(); clazz != null; clazz = clazz.getSuperclass()
  27.         Field dexElementsField =  clazz.getDeclaredField("dexElements");
  28.         //old Element[]
  29.         Object[] dexElements = (Object[]) dexElementsField.get(dexPathList);
  30.         //兼并后的数组
  31.         Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),
  32.                 dexElements.length + patchElements.length);
  33.         // 先拷贝新数组
  34.         System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
  35.         System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);
  36.         //修正 classLoader中 pathList的 dexelements
  37.         dexElementsField.set(dexPathList, newElements);  
复造代码
上述只是根据类减载道理进而对热建回复复兴理停止论述的大抵流程,完成热建复借需求思索到一下兼容成绩、肴纯等内乱容。

免责声明:假如进犯了您的权益,请联络站少,我们会实时删除侵权内乱容,感谢协作!
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,按照目前互联网开放的原则,我们将在不通知作者的情况下,转载文章;如果原文明确注明“禁止转载”,我们一定不会转载。如果我们转载的文章不符合作者的版权声明或者作者不想让我们转载您的文章的话,请您发送邮箱:Cdnjson@163.com提供相关证明,我们将积极配合您!
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并自负版权等法律责任。
回复 关闭延时

使用道具 举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则