Android 虚拟机与类加载机制
虚拟机
- JVM虚拟机
- 是Java中的虚拟机它的指令集是基于堆栈的,运行的是每一个.class
- Dalvik虚拟机
- 是Android中的虚拟机它的指令集是基于寄存器,运行的是所有java字节码通过dx打包工具打成.dex,在Android2.2开始支持JIT及时编译(Just In Time),在程序运行中将热点代码(经常使用)进行优化或者编译.
- ART虚拟机
- ART虚拟机是Android4.4中的一个开发者选项,在5.0中是默认虚拟机.ART引用了一个AOT预先编译机制(Ahead Of Time),在应用安装时使用dex2oat工具将dex字节码编译成机器直接使用的机器码,所以在5.0后会感觉感觉安装应用会慢些.但是在Android N就采用了混合编译,不在安装时进行AOT,而是运行时采用JIT将常用方法记录在Profile中.当系统空闲或充电时,编译守护进程会运行,将Profile中常用的代码进行AOT编译,下次运行时直接使用.
Android中ClassLoader
- PathClassLoader与DexClassLoader区别
- 都是可以加载外部的dex/apk,只不过DexClassLoader可以指定optimizedDirectory,也就是.odex存放的位置
- BaseDexClassLoader 构造
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, librarySearchPath, parent, null, false);
}
BaseDexClassLoaderchan参数的含义
- dexPath 需要加载的文件列表,文件可以是包含了 classes.dex 的 JAR/APK/ZIP,也可以直接使用 classes.dex 文件,多个文件用 “:” 分割
- librarySearchPath 存放需要加载的 native 库的目录
- optimizedDirectory 存放优化后的 dex,可以为空
- parent 父 ClassLoader
通过构造可以知道通过传入的dex文件,然后优化后保存在optimizedDirectory目录
- PathClassLoader 构造
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
通过类的注释可以知道它是系统类也是其它应用程序类的加载器,通过构造可以发现传入optimizedDirectory参数为null
- DexClassLoader 构造 8.0之前与8.0是有所区别的,之前是一个给创建optimizedDirectory参数,一个没有optimizedDirectory参数,所以说8.0之后DexClassLoader与PathClassLoader没有区别了
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
- 类加载流程
- 首先类加载是双亲委派 看下ClassLoader代码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
//检查Class是否被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//如果parent不为空,则用parent的loadClass加载
c = parent.loadClass(name, false);
} else {
//parent为空则用BootClassLoader加载
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.
//如果都找不到就自己加载
c = findClass(name);
}
}
return c;
}
-
这么做的好出就是
- 避免重复加载,当父类加载器已经加载了该类,就没必要子类ClassLoader在加载一次.
- 安全性考虑,防止核心API库被随意篡改.
-
再说一下Android类加载流程
- 首先会走findClass,但是PathClassLoader中没有这个方法会走到BaseDexClassLoader中
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { // First, check whether the class is present in our shared libraries. if (sharedLibraryLoaders != null) { for (ClassLoader loader : sharedLibraryLoaders) { try { return loader.loadClass(name); } catch (ClassNotFoundException ignored) { } } } // Check whether the class in question is present in the dexPath that // this classloader operates on. 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(name, suppressedExceptions)找,这个pathList有是什么他是从构造中创建的就是DexPathList对象
public BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, boolean isTrusted) { super(parent); // Setup shared libraries before creating the path list. ART relies on the class loader // hierarchy being finalized before loading dex files. this.sharedLibraryLoaders = sharedLibraryLoaders == null ? null : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); reportClassLoaderChain(); }
我们继续 调用这个DexPathList findClass 看下代码实现
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那这又是个什么东西,他是从创建DexPathList对象时初始化的
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 //因为可以传多个dexPath他们是用':'分割的所以要进行这个路径分割通过makeDexElements方法获取dexElement数组 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted); //底下省略
这个dexElements就是通过makeDexElements获取的,到这里我们就知道大概了通过遍历dexElements获取element也就是dex,通过DexPathList.findClass就走完加载流程了
public Class<?> findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) { return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null; }
- 参考资料
- https://juejin.cn/post/6844903929562529800