前言
1. class文件与dex文件解析
在学习java虚拟机之前,我们有必要先来了解下下class文件与dex文件。相比大家对这两文件都耳熟能详,但是对于初学者来说却是"听起声而不见其人"。下面我们就来了解下。首先我们先来对他们有个总体的概念:
- class文件:能够被JVM识别,加载并执行的文件格式。
- dex文件 :能够被DVM(Dalvik-Android平台虚拟机)识别,加载并执行的文件格式。
这就是两者大致的作用与概念。下面我们来分别了解下。
1.1 class文件
-
class文件的概括
对于class文件的概念,上面我们提到了,他就是一种文件格式。是一种能被Java虚拟机识别的文件格式,所以在了解虚拟机之前有必要先了解下class文件。Java程序的执行依赖于编译环境和运行环境,下面我们用一张图来了解下流程的完成。
此图来源
-
生成class文件
相信在学习java的时候老师都是要求我们用记事本来编写java代码,然后用命令生成.class文件,然后我们就可以用java命令去执行我们别写的代码了。其实这个过程就对应了我们上面的流程图过程。不过对于生成.class文件的方式有很多。常见有如下:
- 通过IDE自动帮我们build(我们现实中使用的方法)
- 利用命令生成
为了更好的理解与回顾基础,我们在走一遍2.这个流程。
- 编写java文件:
Hellow.java:
public class Hellow {
public static void main(String args[]){
System.out.println("Hellow Android!\n");
}
}
- 这段代码非常简单。下面我们通过命令javac语言编译器:
javac Hellow.java
通过在命令行输入这段命令。我们就可以生成Hellow.class文件。这时我们就可以通过java命令:
java Hellow
后接我们的类名来运行这个.class文件来执行我们的代码。最后输出入下:
现在我们回头看看开头的总结图。就更加明了了.class文件是什么了。
-
class文件结构
在明白了什么是class文件之后我们就来class文件的格式与结构。
首先我们要知道一个class文件它的作用,即:记录一个类文件的所有信息,记住是所有,他与源文件相比更复杂。下面我们来具体看下他的结构:
- 一种8位二进制的一种流文件
- 各个数据按顺序紧密的排列,无间隙(好处是减小文件体积,使JVM读取更迅速)
- 每个类和接口都单独占用一个class文件(方便管理自己)
下面我们就来了解下class文件结构类型:
这张图就是一个.class文件包含的所有类型。这里面比较中的要的就常量池。我们的主要内容都包含在其中。那么constant_pool又有哪些类型呢?我们来简单列举下:
简单类型(基础数据类型常量):
CONSTATN_Integer_info
CONSTATN_String_info
CONSTATN_Long_info
.
.
.
等等
复杂类型:CONSTATN_Class_info(记录class信息)
CONSTATN_Fildref_info(记录所有成员变量)
-
CONSTATN_Methodref_info(记录所有方法信息)
.
.
.
等等
最后这些复杂类型都会包含简单类型。最后指向(通过下标索引指向)具体的常量池常量。这是.class文件的内部结构具体是不是这样的我们通过010Editor软件查看如下:
此图为打开二进制.class文件的结构图。从图中可以看到,一个.class文件正是包含上面我们所列举出来的类型。其中常量池(constant_pool)所占最多,而他的类型(Comment)可以看到正是我们列举出来的。我们打开看下:
我们以cp_info(结构体类型) constant_pool(常量池)为例。 它里面分别包含tag(标记) class_index(表明方法所属那个类) name_and_type_index(方法名与类型) 最后根据索引可以找到他具体的内容。这里我们只需大概有个了解,更详细讲解查考Class文件内容解析 class文件弊端
对于class文件结构大家可能云里雾里。不过大家只需了解就好,待以后深入了解后再慢慢理解。那么他有什么弊端呢?如下:
- 内存占用大,不适合移动端
- 堆栈的加栈模式,加载速度慢
- 文件IO操作多,类查找慢
基于class文件的这些缺点,在移动端就尤为明显。以为我们的移动设备最宝贵的就是内存,因此dex由此诞生,下面我们来理解下dex文件
1.2 dex文件
基于class的缺点,在Android平台移动设备上我们运行在Dalvik即为dex文件。他的作用为记录整个工程所有类文件的的信息。记住是整个工程。下面我们简单了解下如何生成 :
dex文件结构:
- 一种8位二进制的一种流文件
- 各个数据按顺序紧密的排列,无间隙
- 整个应用中的所有java源文件都放在一个dex中
可以看到他与class文件只有最后一点不同。 下面我们来具体看下。
dex文件结构:
从图中我们看到分三个区。文件头:描述了dex文件的基本信息(所有字段大致的分布等);索引区:包含所有相关信息的索引;数据区:索引所指向的位置即在数据区存储,即索引区尽量位置,数据区记录位置所指向的具体数据。这里面的链接数据区即为我们的动态链接库.so文件。下面我们分别对3本分讲解。
dex文件头:
总结: dex文件头记录dex文件大小以及各个区段的起始位置和长度(偏移量)
索引区:
从这幅图中我们可以看到。第一行为头文件,以ids为结尾都是索引区,索引区所指引的值就是value位置中的值。里面包含了所有类的方法,类,以及常量等索引以及集体的数据。
1.3 两者的异同
- 本质上他们都是一样的。dex是从class文件演变而来的
- class文件存在许多冗余信息,dex会取出冗余,并整合。
针对第2点。我们可以从这几方面理解。首先,class文件时一个类或者接口为一个class文件而dex是所有文件合并成一个dex文件,比我们从图中的结构,也看直观的看到dex文件要比class文件更简洁,所以当类越来越多则class文件也会越来越多,而dex文件会将多个class文件整合到一个dex文件。如下图:
在class文件越来越多的时候 dex文件的3大区都不会增加。而.class文件会随着文件增多,每个文件的结构又独立,所以一定会产生很多冗余重复数据。
3. Java虚拟机
理解JVM我们先来看下他的结构:
详细结构:
2个图表达的含义是一样的。
从图中可以分为三部分。第一部分类加载器子系统将class文件进行加载,第二部分类加载器将加载后class字节码数据存放在内存空间,JVM将内存空间分成5部分,同时我们的垃圾回收机制(gc)也是回收这部分的内存占用。第三部分比较底层,是与一些系统CPU交互等。所以第三部分我们占不做分析。
3.1 将.class文件载入类加载器
这部分主要就是讲.java源文件转化为.class字节码文件。如何生成前面已经提过。具体细节如下:
这些生成都是javac来完成的 javac就是java编译器
3.2 类加载器
3.1.2 类加载器有哪些
类加载指将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后在堆上创建java.lang.Class对象,封装类在方法区内的数据结构类装载器子系统负责查找并装载类型信息。其实Java虚拟机有两种类装载器:系统装载器(前两个)和用户自定义装载器(后两个)。前者是Java虚拟机实现的一部分,后者则是Java程序的一部分。
- 启动类加载器(BootstrapClassLoader):在JVM运行时被创建,负责加载存放在JDK安装目录下的jre\lib的类文件
- 扩展类加载器(Extension ClassLoader):该类加载器负责加载JDK安装目录下的\jre\lib\ext的类
- 应用程序类加载器(AppClassLoader):负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有定义过自己的类加载器,该类加载器为默认的类加载器。
- 用户自定义类加载器(User ClassLoader):开发人员通过拓展ClassLoader类定义的自定义加载器,加载程序员定义的一些类。
类加载指将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后在堆上创建java.lang.Class对象,封装类在方法区内的数据结构。和其它对象一样,用户自定义的类装载器以及Class类的实例放在内存中的堆区,而装载的类型信息则位于方法区。
-
委派模式(Delegation Mode)
它是JVM加载类的模式(向上查找,向下加载)。当JVM加载一个类的时候,下层的加载器会将将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查完了之后,按照相反的顺序进行加载,如果Bootstrap加载器找不到这个类,则往下委托,直到找到类文件。对于某个特定的类加载器来说,一个Java类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是(classLoader,package,className)。一个类可以被不同的类加载器加载。
如果我们将这个类放到JAVA_HOME/jre/lib/ext这个路径中去(相当于交给Extension加载器加载),按照同样的规则,最后由Extension加载器加载MyClass类。此类一共加载2次。但是每次都是由不同的ClassLoader完成。
总结: 1和2加载器是加载加载jdk相关的class文件。3是加载应用用到的class文件,4是我们自定义类加载器,可以加载指定的class文件。
3.1.3 加载流程
类加载器除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及解析符号引用。这些动作还需要按照以下顺序进行:
3.2 运行时数据区(也就是内存管理)
这部分是整合JVM的核心,也是GC垃圾回收的的主要部分,所以单独在下一篇讲解。
3.3 执行引擎
类加载器将字节码载入内存之后,执行引擎以Java字节码指令为单元,读取Java字节码。问题是,现在的java字节码机器是读不懂的,因此还必须想办法将字节码转化成平台相关的机器码。这个过程可以由解释器来执行,也可以有即时编译器(JIT Compiler)来完成。
优秀文章推荐(本文部分参考)
Java虚拟机工作原理
理解Java虚拟机体系结构
Java虚拟机工作原理详解
书籍:《深入理解java虚拟机》(有需要PDF请留言,不过希望大家支持正版)