唯一有点特别之处的是常量池。什么东西会放在常量池呢?最容易想到的就是字符串了。对头,这个Java源码中的类名,方法名,变量名,居然都是以字符串形式存储在常量池中。所以,图2中的this_class和super_class分别指向两个字符串,代表本类的名字和基类的名字。这两个字符串存储在常量池中,所以this_class和super_class的类型都是u2(索引,代表长度为2个字节)。

Class文件用javap工具可以很好得解析成图2那样的格式,我这里替大家解析了一把,结果如图3所示(先显示部分内容):

注意,解析方法为:javap -verbose xxxx.class

先来看看常量池。

2.1.1 常量池介绍

常量池看起来陌生,其实简单得要死。注意,count_pool_count是常量池数组长度+1。比如,假设某个Class文件常量池只有4个元素,那么count_pool_count=5)。

javap解析class文件的时候,常量池的索引从1算起,0默认是给VM自己用得,一般不显示0这一项。这也是为什么图3中常量池第一个元素以#1开头。所以,如果count_pool_count=5的话,真正有用的元素是从count_pool[1]到count_pool[4]。

常量池数组的元素类型由下面的代码表示:

cp_info { //特别注意,这是介绍的cp_info是相关元素类型的通用表达。

u1 tag; //tag为1个字节长。不论cp_info具体是哪种,第一个字节一定代表tag

u1 info[]; //其他信息,长度随tag不同而不同

}

//tag取值,先列几个简单的:

tag=7 <==info代表这个cp_info是CONSTANT_Class_info结构体

tag=9<==info代表CONSTANT_Fieldrefs_info结构体

tag=10<==info代表CONSTANT_Methodrefs_info结构体

tag=8<==info代表CONSTANT_String_info结构体

tag=1<==info代表CONSTANT_Utf8_info结构体

在JVM规范中,真正代表字符串的数据结构是CONSTANT_Utf8_info结构体,它的结构如下代码所示:

CONSTANT_Utf8_info {

u1 tag;

u2 length; //下面就是存储UTF8字符串的地方了

u1 bytes[length];

}

大家看图3中常量池的内容,比如#2=Utf8 com/test/TestMain 这行表示:

数组第二个元素的类型是CONSTANT_Utf8_info,字符串为“com/test/TestMain”

下面我们看几个常用的常量池元素类型

(1) CONSTANT_Class_info

这个类型是用于描述类信息的,此处的类信息很简单,就是类名(也就是代表类名的字符串)

CONSTANT_Class_info {

u1 tag; //tag取值为7,代表CONSTANT_Class_info

u2 name_index; //name_index表示代表自己类名的字符串信息位于于常量池数组中哪一个,也就是索引

}

唉,够懒的,name_index对应的那个常量池元素必须是CONSTANT_Utf8_info,也就是字符串。图3中的例子,咱们再看看:

#1 = Class #2 //com/test/TestMain

#2 = Utf8 com/test/TestMain

这说明:

常量池第一个元素类型为Class_info,它对应的name_index取值为2,表示使用第2个元素

常量池第二个元素类型为Utf8 内容为“com/test/TestMain”

#1最后的//表示注释,它把第二行的字符串内容直接搬过来,方便我们查看

(2) CONSTANT_NameAndType_Info

这个结构也是常量池数据结构中中比较重要的一个,干什么用得呢?恩,它用来描述方法/成员名以及类型信息的。有点JNI基础的童鞋相信不难明白,在JNI中,一个类的成员函数或成员变量都可以由这个类名字符串+函数名字符串+参数类型字符串+返回值类型来确定(如果是成员变量,就是类名字符串+变量名字符串+类型字符串)来表达。既然是字符串,那么NameAndType_Info也就是存储了对应字符串在常量池数组中的索引:

CONSTANT_NameAndType_info {

u1 tag;

u2 name_index; //方法名或域名对应的字符串索引

u2 descriptor_index; //方法信息(参数+返回值),或者成员变量的信息(类型)对应的字符串索引

}

//还是来看图3中的例子吧

#13 = Utf8 ()V

#15 = NameAnType #16.#13 //合起来就是test.()V 函数名是test,参数和返回值是()V

#16=Utf8 test

太简单了,都不惜得说...,请大家自行解析#25这个常量池元素的内容,一定要做喔!

注意,对于构造函数和类初始化函数来说,JVM要求函数名必须是和。当然,这两个函数是编译器生成的。

(3) CONSTANT_MethodrefInfo三兄弟

Methodref_Info还有两个兄弟,分别是Fieldref_Info,InterfaceMethodref_Info,他们三用于描述方法、成员变量和接口信息。刚才的NameAndType_Info其实已经描述了方法和成员变量信息的一部分,唯一还缺的就是没有地方描述它们属于哪个类。而咱这三兄弟就补全了这些信息。他们三的数据结构如图4所示:

如此直白简单,不解释了。不放心的童鞋们请对照图3的例子自行玩耍!

常量池先介绍到这,它还有一些有用的信息,不过要等到后面我们碰到具体问题时再分析

2.1.2 Field和Method描述

刚才在常量池介绍中有提到Methodref_Info和Fieldref_Info,不过这两个Info无非是描述了函数或成员变量的名字,参数,类型等信息。但是真正的方法、成员变量信息还包括比如访问权限,注解,源代码位置等。对于方法来说,更重要的还包括其函数功能(即这个函数对应的字节码)。

在Java VM中,方法和成员变量的完整描述由如图5所示的数据结构来表达的:

access_flags:描述诸如final,static,public这样的访问标志

name_index:方法或成员变量名在常量池中对应的索引,类型是Utf8_Info

attribute_info:是域或方法中很重要的信息。我们单独用一节来介绍它。

2.1.3 attribute_info介绍

attribute_info结构体很简单,如下代码所示:

attribute_info {//特别注意,这里描述的attribute_info结构体也是具体属性数据结构的通用表达

u2 attribute_name_index; //attribute_info的描述,指向常量池的字符串

u4 attribute_length; //具体的内容由info数组描述

u1 info[attribute_length];

}

Java VM规范中,attribute类型比较多,我们重点介绍几个,先来看代表一个函数实际内容的Code属性。

(1) Code属性

代表Code属性的数据结构如图6所示:

前2个成员变量就不多说了。属于attribute的头6个字节,分别指向代表属性名字符串的常量池元素以及后续属性数据的长度。注意,Code属性的attribute_name_index所指向的那个Utf8常量池元素对应的字符串内容就是“Code”,大家可参考图3的#9。

max_stack和max_locals:虚拟机在执行一个函数的时候,会为它建立一个操作数栈。执行过程中的参数啊,一些计算值啊等都会压入栈中。max_stack就表示该函数执行时,这个栈的最大深度。这是编译时就能确定的。max_locals用于描述这个方法最大的栈数和最大的本地变量个数。本地变量个数包括传入的参数。

code_length和code:这个函数编译成Java字节码后对应的字节码长度和内容。

exception_table_length:用来描述该方法对应异常处理的信息。这块我不打算讲了,其实也蛮简单,就是用start_pc表示异常处理时候从此方法对应字节码(由code[]数组表示)哪个地方开始执行。

Code属性本身还能包含一些属性,这是由attributes_count和attributes数组决定的。

来看个实际例子吧,如图7所示(接着图3的例子):

图7中:

stack=2,locals=2,args_size=1。结合代码,main函数确实有一个参数,而且还有一个本地变量。注意,main函数是static的。如果对于类的非static函数,那么locals的第0个元素代表this。

stack后面接下来的就是code数组,也就是这个函数对应的执行代码。0表示code[]的索引位置。0:new:代表这个操作是new操作,此操作对应的字节码长度为3,所以下一个操作对应的字节码从索引3开始。

LineNumberTable也是属性的一种,用于调试,它将源码和字节码匹配了起来。比如line 7: 0这句话代表该函数字节码0那一个操作对应代码的第7行。

LocalVariableTable:它也是属性一种,用于调试,它用于描述函数执行时的变量信息。比如图7中的Start = 0:表示从code[]第0个字节开始,Length = 13表示到从start=0到start+13个字节(不包含第13个字节,因为code数组一共就12个字节)这段范围内,这个变量都有效(也就是这个变量的作用域),Slot=0表示这个变量在本地变量表中第一个元素,还记得前面提到的locals吗?,name为“args”,表示这个参数的名字叫args,类型(由Signature表示)就是String数组了。

请大家自行解析图7中最后一行,看看能搞明白LocalVariableTable的含义不...

另外,Android SDK build Tools中的dx工具dump class文件得到的信息更全,大家可以试试。

使用方法是:dx --dump --debug xxx.class。

Class文件先介绍到这,下面我们来看看Android平台上的dex文件。

2.2 Dex文件结构和Odex

2.2.1 dex文件结构简介

Android平台中没有直接使用Class文件格式,因为早期的Anrdroid手机内存,存储都比较小,而Class文件显然有很多可以优化的地方,比如每个Class文件都有一个常量池,里边存储了一些字符串。一串内容完全相同的字符串很有可能在不同的Class文件的常量池中存在,这就是一个可以优化的地方。当然,Dex文件结构和Class文件结构差异的地方还很多,但是从携带的信息上来看,Dex和Class文件是一致的。所以,你了解了Class文件(作为Java VM官方Spec的标准),Dex文件结构只不过是一个变种罢了(从学习到什么程度为止的问题来看,如果不是要自己来解析Dex文件,或者反编译/修改dex文件,我觉得大致了解下Dex文件结构的情况就可以了)。图8所示为Dex文件结构的概貌:

有一点需要说明:传统Class文件是一个Java源码文件会生成一个.Class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,如此,多个Class文件里如果有重复的字符串,当把它们都放到一个dex文件的时候,只要一份就可以了嘛。

dex头部信息中的magic取值为“dexn035 ”

proto_ids:描述函数原型信息,包括返回值,参数信息。比如“test:()V”

methods_ids:函数信息,包括所属类及对应的proto信息。比如

"Lcom.test.TestMain. test:()V",.前面是类信息,后面属于proto信息

下面我们将示例TestMain.class转换成dex文件,然后再用dexdump工具看看它的结果,如图9所示:

具体方法:

java dalvik_深入理解Android之Java虚拟机Dalvik相关推荐

  1. android r.java 原理,深入理解Android消息处理系统原理

    Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制.实际上谷歌参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制. Android通过Looper.Ha ...

  2. Java异常处理深入理解_关于java异常处理机制的深入理解.doc

    关于java异常处理机制的深入理解.doc 关于JAVA异常处理机制的深入理解1引子TRYCATCHFINALLY恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解.不过 ...

  3. java之php、Android、JAVA、C# 3DES加密解密

    异常如下 1.javax.crypto.BadPaddingException: Given final block not properly padded 1)要确认下是否加密和解密都是使用相同的填 ...

  4. Java多线程 -- 深入理解JMM(Java内存模型) --(五)锁

    [转载自并发编程网 – ifeve.com 原文链接:http://ifeve.com/tag/jmm/] 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机 ...

  5. 自制java虚拟机_《深入理解Android:Java虚拟机ART》 —1.2.3 准备模拟器和自制系统镜像...

    1.2.3 准备模拟器和自制系统镜像 阅读源码是学习虚拟机的主要方法.但在某些关键地方,有时候很难确定代码逻辑的走向,这时就需要在源码中加一些日志来辅助我们观察虚拟机的行为.在此,笔者推荐使用模拟器和 ...

  6. 安卓 java内存碎片_理解Android Java垃圾回收机制

    Jvm(Java虚拟机)内存模型 从Jvm内存模型中入手对于理解GC会有很大的帮助,不过这里只需要了解一个大概,说多了反而混淆视线. Jvm(Java虚拟机)主要管理两种类型内存:堆和非堆. 堆是运行 ...

  7. java的接口理解_原来Java的接口可以这样理解

    为什么写这篇文章 今天有人问了我这样一个问题 Java中为什么要使用接口呢? 还要先定义了一个接口,类还要实现接口的方法,还不如直接在这个类中写实现方法呢,根本没必要定义接口啊. 大概就是这样一个问题 ...

  8. android java打开wap链接,Android 链接 java 服务无法打开链接的有关问题

    问题描述: 最近研究Android, 在android端连接本地java服务时,总是链接失败,不能打开链接, 但是链接百度等其他网址时却能正确显示数据. 打开链接的代码如下: String url = ...

  9. java 围棋_开源Android围棋java源码

    更多:开源围棋- 掌中围棋2.4版本源码,包含一个完善的SGF棋谱文件解析器,速度快,内存占用低适合Android使用 https://github.com/uestccokey/HandsGo SG ...

最新文章

  1. kuayu react_react跨域解决方案
  2. android 平板安装程序开发者,android – 限制平板电脑中的应用安装
  3. python可以用break作为变量名_Python初体验(一)—【配置环境变量】【变量】【input】【条件语句】【循环语句】...
  4. iOS开发 简述使用OCUnit对程序进行单元测试(UnitTest)
  5. 温州大学《深度学习》课程课件(七、卷积神经网络基础)
  6. 7.24 杭州站 | 阿里云 Serverless Developer Meetup
  7. java 参数命名冲突_Java中的命名参数
  8. python 服务端判断客户端异常断开
  9. sql语句如何拼接Java变量
  10. 4分用计算机算,GPA计算器:如何将平时成绩换算成四分制GPA
  11. android 蒙版图片带拖动_使用jQuery draggable在div剪切蒙版中拖动缩放图像?
  12. fastdfs fild_id
  13. nmn是真的还是假的,如何鉴别高质量的nmn,方法一览
  14. PAT(甲级) 1003. Emergency
  15. 转:关于食品090930
  16. 文件夹总是在新窗口打开
  17. 全球与中国医疗计费软件市场深度研究分析报告
  18. 转贴:华为加班死人了
  19. [故事]只会写自己名字的港大院士(图)
  20. 10大电子书免费下载网站

热门文章

  1. Linux环境下C语言模拟内存负载测试
  2. 订阅基础:RSS、ATOM、FEED、聚合、供稿、合烧与订阅
  3. 404 – File or directory not found.
  4. linux教程:通过编译安装ansible解决apt install ansible后无法安装AWX的莫名问题
  5. docker-compose安装部署ELK
  6. Scala集合常用方法:fold折叠
  7. idea设置启动时打开欢迎页
  8. IDEA设置使用git bash替换原有terminal(cmd)
  9. 基于Springboot2.0的Dubbo入门项目(dubbo-spring-boot-starter)
  10. 【已解决】R read.table()报错:incomplete final line found by readTableHeader