java虚拟机与安卓虚拟机的区别

目录

整体区分

Dailk和ART

安卓中的类加载器

类加载机制采用双亲委托机制



整体区分

JVM是基于栈的虚拟机;而安卓是基于寄存器的虚拟机(ART和davik)。

1、寄存器 

上边这个a+b在寄存器中的运行方式是:从物理地址为100的位置拿到第一个数放到寄存器AX中,物理地址是104的地方拿到第二个数存到BX中,,然后再ALU里边完成算数相加,将结果存到寄存器CX中,最后将结果地址存在108的位置。寄存器就是一个晶体管。安卓的虚拟机是为了模拟真实的寄存器的操作流程。

        基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放 在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法 调用的活动记录以帧为单位保存在调用栈上。

 相比于JVM。每次都需要将要操作的数据经过操作数栈,安卓这种基于寄存器的方式指令数量明显会减少很多。

同样一个1+2的操作。在JVM里边会是这样的:

安卓上边只有4条指令,而JVM则有多达8条指令。 

基于寄存器的虚拟机开发难度相比基于栈的更高。

Dailk和ART

        DVM也是实现了 JVM 规范的一个虚拟器,默认使用 CMS 垃圾回收器,但是与 JVM 运行 Class 字节码不同, DVM 执行 Dex(Dalvik Executable Format)** —— 专为 Dalvik 设计的一种压缩格式。 Dex 文件是很多 .class 文件处理压 缩后的产物,最终可以在 Android 运行时环境执行。
        而ART Android Runtime 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。 ART Dalvik 都是运行 Dex 字节码的兼容运行时,因此针对 Dalvik 开发的应用也能在 ART 环 境中运作。
dexopt dexaot
dexopt
        在Dalvik 中虚拟机在加载一个 dex 文件时,对 dex 文件 进行 验证 和 优化的操作,其对 dex 文件的优化结果变成了 odex(Optimized dex) 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码。
dex2oat
        ART 预先编译机制 ,在安装时对 dex 文件执行 AOT 提前编译操作,编译为 OAT (实际上是 ELF 文件)可执行 文件(机器码)
        Dalvik虚拟机:执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT及时编译(Just in Time)在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。
        ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。

那么,ART虚拟机执行的本地机器码是从哪里来?

        Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字 节码翻译成本地机器码的最恰当AOT(Ahead Of Time)时机也就发生在应用安装的时候。ART 引入了 ,在安装时,ART 使用设备自带的 dex2oat 工具来编译应用, dex 中的字节码将被编译成本地机器码。
AndroidN的运行方式
ART使用预先(AOT)编译,并且从AndroidN混合使用AOT编译,解释和JIT。
1、最初安装应用时不进行任何AOT编译 (安装又快了) ,运行过程中解释执行,对经常执行的方法进行JIT(其实就是记录的一些类的信息),经过JIT(JIT编译的代码只有本次有效)编译的方法将会记录到Profile 配置文件中。
2、当设备闲置和充电时,编译守护进程会运行,根据 Profile 文件对常用代码进行AOT编译。待下次运行时直接使用。
Dalvik的dex就是dex文件,而ART的odex,已经是机器码了。

安卓中的类加载器

类加载是一个IO操作,所以这里必须要用缓存。加载类还需要解析出来,将类的信息保存到方法区。

类加载机制采用双亲委托机制

有两个好处:一个避免多次加载;而是安全,防止核心类被篡改(如果改掉了系统的核心代码,这可能导致后续出现了不可预测的错误)。

例如我们自定义String类:

所有用到了equals方法的地方都会崩溃!甚至连系统流程都可能被影响。

Android类加载流程

安卓中主要有三个类加载器:BootClassLoader,DexClassLoader, PathClassLoader,第一个用来加载Framework中的类,后两个用来加载自己写的类和谷歌的拓展库,例如AppCompatActivity等,需要注意的是,在Android8之后,DexClassLoader, PathClassLoader已经没有区分了,我们在加载类的时候,用的还是PathClassLoader,之所以还保留DexClassLoader,谷歌可能是考虑的拓展原因。

安卓类加载的流程:

// dalvik.system.BaseDexClassLoader#findClass    
@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
    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,其实是DexPathList。DexPathList实在BaseClassLoader的构造方法中进行的初始化,并且将dex文件的路径传递到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的构造方法:

public DexPathList(ClassLoader definingContext, String librarySearchPath) {
    if (definingContext == null) {
        throw new NullPointerException("definingContext == null");
    }
    this.definingContext = definingContext;
    this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
    this.systemNativeLibraryDirectories =
            splitPaths(System.getProperty("java.library.path"), true);
    // splitDexPath,分组,虽然一般一个apk只有一个dex文件,但是我们是能够支持多个dex文件的同时解析的。传入的格式:/a/a/dex:/a/b.dex。两个dex文件中间用冒号隔开。
    this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
}
// 一个dex生成一个element,多个dex就有多个element,所以这里使用数组。
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

进入DexPathList的查找类的流程:

// dalvik.system.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;
}

接着会走到这里:

public Class<?> findClass(String name, ClassLoader definingContext,
        List<Throwable> suppressed) {
    return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
            : null;
}

接着走到这里:

// DexFile里边:
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
                                              DexFile dexFile)
        throws ClassNotFoundException, NoClassDefFoundError;

最终进入C++层:

// dalvik_system_DexFile.cpp

static jclass DexFile_defineClassNative(JNIEnv* env,
                                        jclass,
                                        jstring javaName,
                                        jobject javaLoader,
                                        jobject cookie,
                                        jobject dexFile) {
  std::vector<const DexFile*> dex_files;
  const OatFile* oat_file;
  if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
    VLOG(class_linker) << "Failed to find dex_file";
    DCHECK(env->ExceptionCheck());
    return nullptr;
  }
  ScopedUtfChars class_name(env, javaName);
  if (class_name.c_str() == nullptr) {
    VLOG(class_linker) << "Failed to find class_name";
    return nullptr;
  }
  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
  for (auto& dex_file : dex_files) {
    const dex::ClassDef* dex_class_def =
        OatDexFile::FindClassDef(*dex_file, descriptor.c_str(), hash);
    if (dex_class_def != nullptr) {
      ScopedObjectAccess soa(env);
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(
          hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));
      ObjPtr<mirror::DexCache> dex_cache =
          class_linker->RegisterDexFile(*dex_file, class_loader.Get());
      if (dex_cache == nullptr) {
        // OOME or InternalError (dexFile already registered with a different class loader).
        soa.Self()->AssertPendingException();
        return nullptr;
      }
      ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),
                                                               descriptor.c_str(),
                                                               hash,
                                                               class_loader,
                                                               *dex_file,
                                                               *dex_class_def);
      // Add the used dex file. This only required for the DexFile.loadClass API since normal
      // class loaders already keep their dex files live.
      class_linker->InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile),
                                                 class_loader.Get());
      if (result != nullptr) {
        VLOG(class_linker) << "DexFile_defineClassNative returning " << result
                           << " for " << class_name.c_str();
        return soa.AddLocalReference<jclass>(result);
      }
    }
  }
  VLOG(class_linker) << "Failed to find dex_class_def " << class_name.c_str();
  return nullptr;
}

整体流程:

直观图:

一定要注意,有几个dex文件,Element数组里边的元素就有几个。

热修复原理

(1)获取当前应用的PathClassLoader

(2)反射获取到DexPathList属性对象pathList;

(3)反射修改pathList的dexElemets

        1、把补丁包的path.dex转化为Element[];(patch)

        2、获得pathList的dexElements属性;(old)

        3、合并patch+old,并反射赋值给pathList的dexElements;patch一定要放在前边

在安卓N之后,由于使用了ART, 常用的都会被编译成机器码,区分Dalvik,不会再去找dex文件,采用了AOT方式,PathClassLoader在创建之初就会将类加载进去,导致使用上边这种方式会失效。此时可以采用的方式是自定义ClassLoader反射替换掉PathClassLoader,修改其中的加载流程。Tinker就采用了这种方式。

参考文章

Android dex、odex、oat、vdex、art区别_慢慢的燃烧的博客-CSDN博客_odex vdex

Android[art]-Android dex,odex,oat,vdex,art文件结构学习总结_TaylorPotter的博客-CSDN博客_vdex文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值