JVM与Android虚拟机解析

有段时间没有更新博客了,事出必有妖,我先来阐述一下写本系列博客的来由:

前段时间老板思想又有点邪恶了,总想破解别人的软件,让我研究研究。于是我在膜拜之余也四方打探,决定从java字节码指令集和dalvik虚拟机的arm指令集入手,虽然后来由于非安全领域专业人员,就放弃了,不过对于技术来说,研究的价值不可遗失,遂成此博客。

当然本篇博客只会分析Java字节码解析和Android机器码解析,让那些邪恶的想法先消停消停,当然,有条件的同学,单凭看懂此篇的智慧,应该也不是什么难事。话归正题,接下来让我们一起揭开JVM和ARM指令集的神秘面纱。


要了解本章内容,先抛出几个问题:

 1.JVM、Dalvik和Art是什么,他们的区别?

 2..class和.dex的区别?

 3.寄存器和虚拟栈是什么,他们的优缺点?

带着以上问题,我们来具体分析一下,首先上一张java虚拟机和Android虚拟机内部结构图如下:

根据以上结构图可知,java虚拟机与Android虚拟机唯一区别是执行引擎不同,jvm执行字节码指令,Android虚拟机执行机器码指令.

有兴趣的朋友,可以深入剖析每个模块的细节,这里贴一张图,不再做过多叙述

为剖析jvm执行引擎与Android虚拟机执行引擎的区别,接下来写一段代码来说明细节:

如下代码,类中写了一个方法,声明了变量相加

public class Test {

    public void sumInt(){
        int a = 2;
        int b = 3;
        int c = a + b;
        return;
    }
}

Java字节码解析

使用编译工具把它变成.class字节码,这个字节码由于是二进制文件,使用文本查看工具是看不了的,需要通过JDK中的javap工具解析,Win+R调出命令行窗口,输入以下指令

javap -v -c -s -l Test.class

可以看到如下图,内容

别的可以不看,直接看红框标注的内容,给它拷贝出来分析:(对于jvm指令,可以参考https://blog.csdn.net/u012070360/article/details/81624854)

基础知识补充:
当int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令

0: iconst_2     //int类型常量2推送至栈顶
1: istore_1     //int类型弹栈到局部变量表中第1个位置等待
2: iconst_3     //int类型常量3推送至栈顶
3: istore_2     //int类型弹栈到局部变量表中第2个位置等待
4: iload_1      //int类型加载局部变量表第1个位置的本地变量推送至栈顶
5: iload_2      //int类型加载局部变量表第2个位置的本地变量推送至栈顶
6: iadd         //int类型相加
7: istore_3     //int类型弹栈到局部变量表中第3个位置等待
8: return       //返回

为便于理解,这里用一张图来辅助分析以上字节码执行流程

Android虚拟机指令解析

同样的,由于.dex是通过.class文件编译形成的,也就是.class对于Android来说是属于中间过渡文件,所以先要在android sdk 下使用dx批处理指令从.class中打包出dex文件(注意:路径要添加一下环境变量,或者把class文件拷贝到sdk这个目录下),然后再将dex文件使用指令解析

这里我们通过命令一气呵成,执行命令:

dx --dex --verbose --dump-to=Test.dex.txt --dump-method=Test.sumInt --verbose-dump Test.class

运行指令后,得到如下结果,其中以local开头的都是在执行命令中打印的日志,实际机器码中是不存在的,我们只需要关注红色框框的内容

整理一下

0000: const/4 v0, #int 2 // #2     //加载int类型的常量2并分配4个字节内存赋给v0
0001: const/4 v1, #int 3 // #3     //加载int类型的常量3并分配4个字节内存赋给v1
0002: add-int v2, v0, v1           //v0和v1相加赋给v2
0004: return-void                  //返回viod类型

为了帮助理解,同样也做了一张图辅助分析

通过以上分析,下面回答上面提到的几个问题:

问题1:

针对JVM和Android虚拟机:

1..class运行在JVM上,.dex运行在Android虚拟机上;

2.JVM运行字节码存在冗余信息,Android虚拟机运行机器码去除了冗余信息;

3.JVM是基于虚拟栈的结构,Android虚拟机是基于寄存器结构;

针对Dalvik和Art:

1.Dalvik环境下需要每次在程序运行是通过即时编译器(JIT)将字节码转换成机器码,即每次都要编译加运行,虽然这样提升了安装速度,但会拖慢应用每次启动的效率;

Art环境下,在安装时字节码就会预编译(AOT)成机器码,虽然安装的时间变长了,但是以后每次启动执行都可以直接运行,运行效率会提高,也就是典型的空间换时间的模式;

2.由于Art通过预编译成机器码,所以会比Dalvik占用更多的存储空间,也由于这个特性,因此不用每次运行前都要先编译,从而减少了CPU的使用率,降低了能耗;

问题2:

1.dex减少了整体的文件尺寸,就像是一个压缩文件,可以表示更多的类,而class则像是单个的文件;

2.Android虚拟机加载类时,只对dex进行一次IO操作可以加载很多类,而多个class则需要多次IO才能完成加载,提高了查找速度;

3.class指令较多,dex指令更加密集;

4.dex指令方便寻址,而class需要多次入栈(load)和弹栈(store)指令;

5.class适用于PC大内存,单指令小的情况快速运行,dex适用于移动设备性能不太高的要求;

问题3:

1.虚拟栈是通过无变量声明来操作虚拟机执行,使用的指令只占一个字节,由于使用栈操作,使指令变得很紧凑,但由于load/store的频繁调用,意味着更多的指令分派次数和内存访问次数,访问内存是执行速度的一个重要瓶颈;

2.寄存器是通过变量寻址来达到操作虚拟机执行,由于需要指定源地址和目标地址,所以每条指令需要占用更多的空间,但总体可以用更少的指令完成操作,指令分派和内存访问次数都较少,但由于访问内存次数较少,数据缓冲更易失效;

综上,基于栈的操作需要更多的指令,而基于寄存器则需要更多的指令空间。栈需要更多的指令意味着需要占用CPU更多的时间,寄存器需要更多指令空间意味着数据缓冲更易失效。是否基于虚拟栈和寄存器最本质的区别就是有无变量声明。

总结

JVM指令明显更多,更消耗CPU,由于每个指令只占1个字节,占用空间少;

Android虚拟机指令少,由于指令分配了源地址和目的地址,使得指令空间变大,更浪费空间

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值