类加载器相关面试题
37.什么是类加载器
ClassLoader是用来动态加载class文件到内存中,虚拟机把描述类的数据从Class文件中加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。加载->验证->准备->初始化->卸载
加载:将类的信息从文件中获取并且载入到JVM内存中
验证:检查读入的结构是否符合JVM规范的描述
准备:分配一个结构用来存储类信息
解析:把这个类的常量池的所有符号引用改变成直接引用
初始化:执行静态初始化程序、类构造器方法的过程
对类进行“初始化“的场景:
-
- 通过 new、getstatic、putstatic、和invokestatic时,需要触发初始化
- 访问类的静态变量和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用的时候。
- 当初始化一个子类时,发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 启动类,执行main函数所在的类会导致该类的初始化。
被动引用:
- 构造某个类的数组时,并不会导致该类的初始化,
- 引用类的静态变量,不会导致类的初始化
1.类的加载过程是通过一个限定名(包名+类名)来获取二进制数据流。
2.类的验证过程是确保class文件的字节流所包含的内容是否符合当前的jvm的规范要求。包括:
- 文件格式验证,这一步的主要目的是保证输入的字节流能正确地解析并存储于方法区之内,这阶段的验证是基于二进制字节流进行的。后面的3个验证是基于方法区的存储结构进行的
- 元数据验证,是对class的字节流进行语义分析的过程(例如:是否存在父类,是否继承了接口等)
- 字节码验证:验证成员的控制流程(如:循环,分支等)
- 符号引用验证
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
- 类接口解析
- 字段的解析
- 类方法的解析
- 接口方法的解析
4.类的初始化阶段:是执行类构造器<client>()方法的过程。
38.类加载器类型(双亲委派机制)
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,但这种父子关系不是以继承的关系来实现,而是都使用组合关系来复用父类加载器。双亲委派机制在保证java程序的稳定运作很重要,它的实现原理是:首先检查是否已经被加载过,若没有,则调用父类加载器的loadClass()方法,若父类加载器为空,则默认使用启动类加载器作为父类加载器,如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
App ClassLoader(应用类加载器或系统加载器):这个加载器使用java实现,使用广泛,负责加载classPath中指定的类。具体的使用场合,在加载classPath中指定的而扩展类加载器没有加载的类,若扩展类加载器加载了classPath中的类,则系统类加载器则没有机会加载。用户定义的类一般都是系统类加载器加载的。可以通过:ClassLoader.getSystemClassLoader()获得
Extension ClassLoader(扩展类加载器):它负责加载Java的标准扩展,一般使用Java实现的,负责加载jre/lib/ext中的类。和普通的类加载器一样。可以通过:ClassLoader.getSystemClassLoader().getParent()获得
BootStrap ClassLoader(引导类加载器):纯C++实现的加载器,没有对应的Java类,它负责加载jdk中jre/lib目录下的系统核心库。对于java程序无法获得它,像上文中获得扩展类加载器的父类加载器是null。像String,Integer,Double类都是由引导类加载器加载的
PathClassLoader:主要用于系统和app的类加载器,其中optimizedDirectory为null,采用默认目录/ data / dalvik-cache /
DexClassLoader:可以从包含classes.dex的jar或者apk中,加载类的类加载器,可用于执行动态加载,但必须是app私有可写目录来缓存odex文件。能够加载系统没有安装的apk或者jar文件,因此很多插件化方案都是采用DexClassLoader;
BaseDexClassLoader:比较基础的类加载器,PathClassLoader和DexClassLoader都只是在构造函数上对其简单封装而已。
BootClassLoader:作为父类的类构造器。
热修复核心逻辑:在DexPathList.findClass()过程,一个类加载器可以包含多个DEX文件,每个DEX文件被封装到一个元素对象,这些元素对象排列成有序的数组dexElements当查找某个类时,会遍历所有的DEX文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的DEX中出现,会优先处理排在前面的DEX文件,这便是热修复的核心精髓,将需要修复的类所打包的DEX文件插入到dexElements前面。
类加载过程常见的ClassNotFound原因:
ABI异常:常见在系统APP,为了减小system分区大小会将apk源文件中的classes.dex文件移除,对于既然可运行在64位又可运行在32位模式的应用,当被强制设置32位时,openDexFileNative在查找不到oat文件时会运行在解释模式,而classes.dex文件不再则出现ClassNotFound异常。
MultiDex处理不当,由于每个Dex文件中方法个数不能超过65536,引入MultiDex机制。dex2oat会自动查找Apk文件中的classes.dex,classes2.dex,…classesN.dex等文件,编译到/data/dalvik-cache下生成oat文件。这里需要文件名跟classesN.dex格式,并且一定要与classes.dex一起放置在第一级目录,有些APP不按照要求来,导致ClassNotFound异常。
39、双亲委托模型(父委托加载机制)
好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。
当加载一个类时,首先会判断当前类是否已经被加载,如果被加载直接返回当前类加载器,如果没有被加载,则把机会让给父类,先让父类加载,若是父类中不能加载,则会去找到Bootstrap加载器,如果Bootstrap加载器加载失败,则会退回上层,自己通过findClass自己去加载对应的路径(这是孝顺型的,先想到父类,但是他们不是通过继承来实现的)
优点: 父委托机制会先去加载系统自带的class,而不会去加载我们自定义的高仿的系统class(比如ArrayList),可以保证我们的程序的安全而不会被恶意注入