android小结

1. Dalvik虚拟机进程可以通过android.os.Process类的静态成员函数start来创建;

        2. Dalvik虚拟机线程可以通过java.lang.Thread类的成员函数start来创建;

        3. 只执行C/C++代码的Native线程可以通过C++Thread的成员函数run来创建;

        4. 能同时执行C/C++代码和Java代码的Native线程也可以通过C++Thread的成员函数run来创建;

   Dalvik虚拟机进程实际上就是通常我们所说的Android应用程序进程。

Android应用程序进程是由ActivityManagerService服务通过android.os.Process类的静态成员函数start来请求Zygote进程创建的,而Zyogte进程最终又是通过dalvik.system.Zygote类的静态成员函数forkAndSpecialize来创建该Android应用程序进程的。

 

一个Dalvik虚拟机线程实际上就是一个Linux线程。

 

至此,我们就分析完成能同时执行C/C++代码和Java代码的Native线程的创建和运行过程了,从中我们就可以得出结论:能同时执行C/C++代码和Java代码的Native线程与只能执行C/C++代码的Native线程一样,都是一个Linux线程,不过区别就在于前者会被附加到Dalvik虚拟机中去,并且具有一个JNI上下文环境,因而可以执行Java代码。

        这样,Dalvik虚拟机进程和线程的创建过程分析就分析完成了,从中我们就可以得到它们与本地操作系统的进程和线程的关系:

        1. Dalvik虚拟机进程就是本地操作系统进程,也就是Linux进程,区别在于前者运行有一个Dalvik虚拟机实例。

        2. Dalvik虚拟机线程就是本地操作系统进程,也就是Linux线程,区别在于前者在创建的时候会自动附加到Dalvik虚拟机中去,而后者在需要执行Java代码的时候才会附加到Dalvik虚拟机中去。

        我们可以思考一下:为什么Dalvik虚拟机要将自己的进程和线程使用本地操作系统的进程和线程来实现呢?我们知道,进程调度是一个很复杂的问题,特别是在多核的情况下,它要求高效地利用CPU资源,并且公平地调试各个进程,以达到系统吞吐量最大的目标。Linux内核本身已经实现了这种高效和公平的进程调度机制,因此,就完全没有必要在Dalvik虚拟机内部再实现一套,这样就要求Dalvik虚拟机使用本地操作系统的进程来作为自己的进程。此外,Linux内核没有线程的概念,不过它可以使用一种称为轻量级进程的概念来实现线程,这样Dalvik虚拟机使用本地操作系统的线程来作为自己的线程,就同样可以利用Linux内核的进程调度机制来调度它的线程,从而也能实现高效和公平的原则。

 

 

Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个JNI方法,那么就会直接跳到它的地址去执行。也就是说,JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行。由此也可看出,JNI方法是Android应用程序与本地操作系统直接进行通信的一个手段。

 

Android应用程序是运行在Dalvik虚拟机里面的,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。除了指令集和类文件格式不同,Dalvik虚拟机与Java虚拟机共享有差不多的特性,例如,它们都是解释执行,并且支持即时编译(JIT)、垃圾收集(GC)、Java本地方法调用(JNI)和Java远程调试协议(JDWP

 

Dalvik虚拟机使用的是dexDalvik Executable)格式的类文件,而Java虚拟机使用的是class格式的类文件。一个dex文件可以包含若干个类,而一个class文件只包括一个类。由于一个dex文件可以包含若干个类,因此它就可以将各个类中重复的字符串和其它常数只保存一次,从而节省了空间,这样就适合在内存和处理器速度有限的手机系统中使用。一般来说,包含有相同类的未压缩dex文件稍小于一个已经压缩的jar文件。

 

   Dalvik虚拟机使用的指令是基于寄存器的,而Java虚拟机使用的指令集是基于堆栈的。基于堆栈的指令很紧凑,例如,Java虚拟机使用的指令只占一个字节,因而称为字节码。基于寄存器的指令由于需要指定源地址和目标地址,因此需要占用更多的指令空间,例如,Dalvik虚拟机的某些指令需要占用两个字节。基于堆栈和基于寄存器的指令集各有优劣,一般而言,执行同样的功能,前者需要更多的指令(主要是loadstore指令),而后者需要更多的指令空间。需要更多指令意味着要多占用CPU时间,而需要更多指令空间意味着数据缓冲(d-cache)更易失效。

 

基于寄存器的指令由于对目标机器的寄存器进行了假设,因此,它更有利于进行AOTahead-of-time)优化。 所谓AOT,就是在解释语言程序运行之前,就先将它编译成本地机器语言程序

 

 

 

不管结论如何,Dalvik虚拟机都在尽最大的努力来优化自身,这些措施包括:

        1. 将多个类文件收集到同一个dex文件中,以便节省空间;

        2. 使用只读的内存映射方式加载dex文件,以便可以多进程共享dex文件,节省程序加载时间;

        3. 提前调整好字节序(byte order)和字对齐(word alignment)方式,使得它们更适合于本地机器,以便提高指令执行速度;

        4. 尽量提前进行字节码验证(bytecode verification),提高程序的加载速度;

        5. 需要重写字节码的优化要提前进行。

        这些优化措施的更具体描述可以参考Dalvik Optimization and Verification With dexopt一文。

        分析完Dalvik虚拟机和Java虚拟机的区别之后,接下来我们再简要分析一下Dalvik虚拟机的其它特性,包括内存管理、垃圾收集、JITJNI以及进程和线程管理。

 

 

内存管理

        Dalvik虚拟机的内存大体上可以分为Java Object HeapBitmap MemoryNative Heap三种。

        Java Object Heap是用来分配Java对象的,也就是我们在代码new出来的对象都是位于Java Object Heap上的。Dalvik虚拟机在启动的时候,可以通过-Xms-Xmx选项来指定Java Object Heap的最小值和最大值。为了避免Dalvik虚拟机在运行的过程中对Java Object Heap的大小进行调整而影响性能,我们可以通过-Xms-Xmx选项来将它的最小值和最大值设置为相等。

 

 

 这个Java Object Heap的最大值也就是我们平时所说的Android应用程序进程能够使用的最大内存。这里必须要注意的是,Android应用程序进程能够使用的最大内存指的是能够用来分配Java Object的堆。

        Bitmap Memory也称为External Memory,它是用来处理图像的。在HoneyComb之前,Bitmap Memory是在Native Heap中分配的,但是这部分内存同样计入Java Object Heap中,也就是说,Bitmap占用的内存和Java Object占用的内存加起来不能超过Java Object Heap的最大值。

此外,在HoneyComb以及更高的版本中,我们可以在AndroidManifest.xmlapplication标签中增加一个值等于“true”的android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Object Heap。事实上,在内存受限的手机上,即使我们将一个应用程序的android:largeHeap属性设置为“true”,也是不能增加它可用的Java Object Heap的大小的,而即便是可以通过这个属性来增大Java Object Heap的大小,一般情况也不应该使用该属性。为了提高系统的整体体验,我们需要做的是致力于降低应用程序的内存需求,而不是增加增加应用程序的Java Object Heap的大小,毕竟系统总共可用的内存是固定的,一个应用程序用得多了,就意味意其它应用程序用得少了。

 

 

垃圾收集(GC)

        Dalvik虚拟机可以自动回收那些不再使用了的Java Object,也就是那些不再被引用了的Java Object。垃圾自动收集机制将开发者从内存问题中解放出来,极大地提高了开发效率,以及提高了程序的可维护性

 

 

即时编译(JIT)

        前面提到,JIT是相对AOT而言的,即JIT是在程序运行的过程中进行编译的,而AOT是在程序运行前进行编译的。在程序运行的过程中进行编译既有好处,也有坏处。好处在于可以利用程序的运行时信息来对编译出来的代码进行优化,而坏处在于占用程序的运行时间,也就是说不能花太多时间在代码编译和优化之上。

 

 

. Java本地调用(JNI)

        无论如何,虚拟机最终都是运行在目标机器之上的,也就是说,它需要将自己的指令翻译成目标机器指令来执行,并且有些功能,需要通过调用目标机器运行的操作系统接口来完成。这样就需要有一个机制,使得函数调用可以从Java层穿越到Native层,也就是C/C++层。这种机制就称为Java本地调用,即JNI。当然,我们在执行Native代码的时候,有时候也是需要调用到Java函数的,这同样是可以通过JNI机制来实现。也就是说,JNI机制既支持在Java函数中调用C/C++函数,也支持在C/C++函数中调用Java函数。

        事实上,Dalvik虚拟机提供的Java运行时库,大部分都是通过调用目标机器操作系统接口来实现的,也就是通过调用Linux系统接口来实现的。例如,当我们调用android.os.Process类的成员函数start来创建一个进程的时候,最终会调用到Linux系统提供的fork系统调用来创建一个进程。

 

 

进程和线程管理

        一般来说,虚拟机的进程和线程都是与目标机器本地操作系统的进程和线程一一对应的,这样做的好处是可以使本地操作系统来调度进程和线程。进程和线程调度是操作系统的核心模块,它的实现是非常复杂的,特别是考虑到多核的情况,因此,就完全没有必要在虚拟机中提供一个进程和线程库。

        Dalvik虚拟机运行在Linux操作系统之上。我们知道,Linux操作系统并没有纯粹的线程概念,只要两个进程共享同一个地址空间,那么就可以认为它们同一个进程的两个线程。Linux操作系统提供了两个forkclone两个调用,其中,前者就是用来创建进程的,而后者就是用来创建线程的。关于Linux操作系统的进程和线程的实现,可以参考在前面Android学习启动篇一文中提到的经典Linux内核书籍

 

 

 关于Android应用程序进程,它有两个很大的特点,下面我们就简要介绍一下。

        第一个特点是每一个Android应用程序进程都有一个Dalvik虚拟机实例。这样做的好处是Android应用程序进程之间不会相互影响,也就是说,一个Android应用程序进程的意外中止,不会影响到其它的Android应用程序进程的正常运行。

        第二个特点是每一个Android应用程序进程都是由一种称为Zygote的进程fork出来的。Zygote进程是由init进程启动起来的,也就是在系统启动的时候启动的。Zygote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android应用程序进程的创建过程很快,而且由于所有的Android应用程序进程都共享同一套Java核心库而节省了内存空间。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值