每日分享Android面试题
1、类的加载过程,Person person = new Person();为例进行说明?
- 因为new用到了Person.class,所以会先找到Person.class文件,并加载到内存中;
- 执行该类中的static代码块,如果有的话,给Person.class类进行初始化;
- 在堆内存中开辟空间分配内存地址;
- 在堆内存中建立对象的特有属性,并进行默认初始化;
- 对属性进行显示初始化;
- 对对象进行构造代码块初始化;
- 对对象进行与之对应的构造函数进行初始化;
- 将内存地址付给栈内存中的p变量
2、类的加载机制是怎么样的呢?
- Java程序,都是由若干个.class文件组织而成的一个完整的Java应用程序,
- 当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能
- 而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常
- 而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的
- 从而只有class文件被载入到了内存之后,才能被其它class所引用
- 所以ClassLoader就是用来动态加载class文件到内存当中用的。
- ClassLoader使用的是双亲委托模型来搜索类
3、Android类加机制是什么样的呢?(热更新的机制是什么样的呢,hotFix\andFix机制是什么样的呢)?
- 对于Android而言,最终的apk文件包含的是dex类型的文件
- dex文件是将class文件重新打包
- 打包的规则又不是简单地压缩,而是完全对class文件内部的各种函数表,变量表进行优化,产生一个新的文件,即dex文件。
- 因此加载这种特殊的Class文件就需要特殊的类加载器DexClassLoader
- 热更新就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统就会选择其中一个类。
- 一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类
- 在此基础上,我们构想了热补丁的方案,把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面,当然系统就就会选择我们要更新的类了
- 当然每个热更新框架都会有差异
- 比如说QZone就是严格按照上述思路进行的
- 而hotFix\andFix采用native hook的方式,这套方案直接使用dalvik_replaceMethod替换class中方法的实现。由于它并没有整体替换class, 而field在class中的相对地址在class加载时已确定,所以AndFix无法支持新增或者删除filed的情况(通过替换init与clinit只可以修改field的数值)。Andfix可以支持的补丁场景相对有限,仅仅可以使用它来修复特定问题。
- 微信Tinker就要高明一些,在编译时通过新旧两个Dex生成差异path.dex。在运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中。为了补丁包尽量的小,微信自研了DexDiff算法,它深度利用Dex的格式来减少差异的大小。当然看起来很美好的总有些差强人意的事情出现,比如tinker对小米的支持就不太好。
4、追问2双亲机制原理是什么样的呢?
- ClassLoader使用的是双亲委托模型来搜索类的
- 每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系
- 虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。
- 当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的
- 首先由最顶层的类加载器Bootstrap ClassLoader试图加载
- 如果没加载到,则把任务转交给Extension ClassLoader试图加载
- 如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。
- 如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。
- 否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
5、为什么要使用双亲委托这种模型呢?
- 因为这样可以避免重复加载
- 当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 考虑到安全因素,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患
- 而双亲委托的方式,就可以避免这种情况
- 因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载
- 所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。
6、JVM在搜索类的时候,又是如何判定两个class是相同的呢?
- JVM在判定两个class是否相同时,不仅要判断两个类名是否相同
- 而且要判断是否由同一个类加载器实例加载的。
- 只有两者同时满足的情况下,JVM才认为这两个class是相同的。
- 就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载
- JVM也认为不相同。
- 比如网络上的一个Java类Glide
- javac编译之后生成字节码文件Glide.class
- ClassLoaderA和ClassLoaderB这两个类加载器并读取了Glide.class文件
- 并分别定义出了java.lang.Class实例来表示这个类
- 对于JVM来说,它们是两个不同的实例对象
- 但它们确实是同一份字节码文件如果试图将这个Class实例生成具体的对象进行转换时
- 就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型