Android里的JVM- 有关ART、Dalvik、JNI的一些窥探

目录

前言

正文

Dalvik虚拟器 --DVM

Android运行时 --ART

运行C、C++代码 --JNI


前言

Android系统可以运行java、kotlin编写的代码,甚至c语言代码,本质上同也是有一个jvm虚拟机来执行代码,JNI则充当了Java代码和本地代码之间的桥梁,让两者可以互相调用,本文是对于Android虚拟机、JNI的一些探究。

正文

Dalvik虚拟器 --DVM

在新的Android版本不再使用。

由Dan Bornstein设计并编写,是一个开源软件,Dalvik的名字来自他的先祖居住过的冰岛渔村,最开始时被设计用于在RAM和CPU较低的嵌入式设备执行Java代码。

我们的java / kotlin 代码被经由如下处理:

kotlin、java --》 .class字节码 --》 .dex文件(包含Dalvik字节码) --》 Android虚拟机中运行


前文提到,Dalvik是一个开源软件,这个最开始是专门为低配(低RAM,CPU)嵌入式设备设计以运行Java代码的,class字节码被转为dalvik字节码,并存在Dalvik EXecutable(.dex文件)Optimized Dalvik EXecutable(.odex文件)。

Dalvik VM是基于寄存器架构的:即使用寄存器存储操作数,指令直接操作寄存器中的数据,由于需要指定操作数寄存器,指令也一般更长且复杂,但是减少了数据移动的开销,指令数量较少。与此对应的是堆栈机器架构--JVM就是,堆栈机器架构使用操作数栈来计算,指令会存栈中弹出操作数,并把结果压入栈内,这样的指令会更短,更简单,但是也会需要更多指令来完成复杂操作。

同时,dx工具用来把class字节码转成dex格式的,多个字节码文件会被抱在一个dex文件内,同时,字节码文件里使用的重复字符串和常量也会被确保只在dex文件存在一个、字节码也被转为DVM使用的指令集、简单的数据结构和函数也会被内联来进一步优化。

在Android2.2开始,DVM支持基于追踪的实时JIT,每次运行程序时持续分析应用程序,并动态将字节码频繁执行短段便以为机器码,来优化程序执行。


But,两个方面让谷歌选择弃用Dalvik VM:

  1. 性能一般:2011年,甲骨文公司测试DVM的性能落后于Hotspot虚拟机,落后越2.2 - 3倍
  2. 对于Android来说,谷歌认为DVM太精简了。
Android运行时 --ART

Android5.0正式取代Dalvik

与Dalvik不同,ART引入了提前编译AOT,在安装时将整个应用程序编译为机器码,这样比JIT又提高了整机效率,降低了功耗。但是JIT功能也被保留,用于识别热代码来提高运行时性能,在设备闲置、充电时AOT负责把代码编译成机器码,不常用代码依靠JIT

同时为了保持向后兼容,ART使用Dalvik相同的输入字节码,dex文件将作为Apk文件的一部分提供,odex文件被替换为elf可执行、可链接格式的.oat文件,在应用程序安装时,ART会把应用程序的dex文件预编译成机器码,存储格式为elf(dex2oat),避免了JIT的开销,但是会让安装速度变慢,存储需求变多。

并且,ART提供大量的应用开发和调试能力,如:

  • 查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程。
  • 询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考。
  • 过滤特定实例的事件(如断点)。
  • 查看方法退出(使用“method-exit”事件)时返回的值。
  • 设置字段观察点,以在访问和/或修改特定字段时暂停程序执行。

以及在发生应用程序错误时给出更多的上下文信息。

运行C、C++代码 --JNI

JNI全名为Java Native Interface Java本地接口,用于让虚拟机的java程序可以与本地应用、库互相调用。

通常,一些音视频编码解码,文件I/O,耗时操作的代码会采用C来编写,以获得更好的性能,JNI使得Java程序访问Native对象,Native方法创建Java对象等。

JNI函数指针表:

JVM的一种数据结构,包含了一组函数指针,指向JVM提供的JNI函数,这些函数用于执行各种如对象创建、方法调用、异常处理等操作

JNIEnv指针:

指向JNI函数表的执行,将传给本地代码,本地代码通过这个指针可以访问和调用JNI函数,JNIEnv对象是线程安全的,且每个线程都有自己独立的JNIEnv。


JNI的核心是如何从Java虚拟机找到并调用相应的本地方法。这通过函数名称的匹配和动态链接库(DLL或SO)的加载来完成:

函数名称匹配:Java方法名和本地方法名之间存在一个映射关系,JNI函数的命名规则是Java_全限定类名_方法名。例如,Java类com.example.MyClass的native方法myMethod的JNI函数名是Java_com_example_MyClass_myMethod。

动态链接库加载:当Java代码调用System.loadLibrary("nativeLib")时,JVM会使用操作系统的动态链接库加载机制(如Linux的dlopen或Windows的LoadLibrary)加载共享库。之后,JVM通过符号表查找函数名(如Java_Example_sayHello),并将其映射到相应的内存地址。

当Java程序调用一个Native方法时,JVM会根据方法名称和签名,在加载的本地库中查找相应的函数地址,并将一个JNIEnv指针传递给本地函数,使其可以调用JNI API,本地函数执行后,返回结果给JVM。JNI提供的API使用方法如 (*env)->GetStringUTFChars() : 将Java字符串转换为C字符串。

如果在本地代码中出现异常,JNI也提供了相应的API可以将异常信息传回JVM。JVM会根据异常信息决定是否抛出异常或执行其他操作。

在Java代码中,通过System.loadLibrary()加载本地库(例如 .so 文件),然后调用本地代码中的函数的过程中,虚拟机Davlik、ART负责管理这些JNI调用,确保Java和本地代码之间的数据交换和执行过程正确无误。被调用的C/C++代码在设备的本地处理器上执行,直接与操作系统和硬件交互。


.so文件是一种共享库文件格式,常见于Unix和Linux系统,包括Android。

so文件支持动态链接,在运行时将共享库加载到应用程序中,而不是在编译时将其嵌入到可执行文件中。通过JNI,Java、Kotlin代码可以用,System.loadLibrary("library_name")来加载so文件,调用.so文件中的函数来执行任务,加载后的库文件在应用运行期间驻留在内存中。

使用JNI有如下注意点

  • JVM没有对Native代码提供gc机制,native代码要自己处理好内存回收。
  • 使用JNI过程的BUG很难调试。
  • Native方法不会被JVM内联,也不会被JIT优化。
  • 在JNI中,所有从Java传递到本地代码的引用都是局部引用,意味着它们的生命周期只限于本地方法调用期间。为了在方法调用之外保存引用,JNI提供了全局引用(NewGlobalRef、DeleteGlobalRef)和弱全局引用(NewWeakGlobalRef、DeleteWeakGlobalRef)的机制。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏目艾拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值