浅析Java卡应用执行机制及虚拟机能效优化

2015-05-30 12:02崔岩卢丽敏
中国新通信 2015年17期
关键词:虚拟机优化

崔岩 卢丽敏

【摘要】 面对日益增多的Java卡应用,不同智能卡厂家开发的Java卡在使用过程中表现出参差不齐的执行效率。通过对Java卡系统体系和实现方法研究发现,Java卡的执行能效受整体硬件(CPU、RAM、FLASH等)、软件(Java卡虚拟机和Java应用)条件所影响,但基于实际的市场环境,一味提升卡片硬件性能和约束开发方的应用编写习惯,都不是一个可行、有效的提高运行效率的方法。因此本文将通过解析一个简单Java卡应用实例是如何被Java卡虚拟机解析及执行的过程,来探讨优化Java卡虚拟机执行效率的简单思

【关键词】 JAVA卡 虚拟机 执 行能效 优化

一、引言

自2010年国内运营商开始逐渐使用搭载Java虚拟机的智能卡产品至今已超过5年之久,累计产品发行量过亿张。同时,伴随近年来移动支付的蓬勃发展,越来越多的Java卡应用如雨后春笋般出现并应用在Java智能卡中,使重多用户享受到Java智能卡及应用的便利,那么这些Java应用是如何在Java智能卡上运行的?以及如何提升Java卡核心——Java卡虚拟机的执行能效,将是本文向大家阐述的重点内容。

二、Java卡及Java卡应用

2.1为什么要使用Java卡?

ORACLE公司的Java编程语言已逐渐成为除Microsoft公司的c语言体系之外,另一种主流的商业软件开发语言。其崛起与发展如此迅猛,既有Java作为开发语言本身所具备的诸多优势,也与Java体系开发成本低、易于部署等诸多特点息息相关。Java语言诉求实现“应用与平台无关系性”——将上层应用与底层平台(操作系统及硬件)剥离开,以实现“一次编写,任意运行(write once, run anywhere)”的愿景,为了达到这一目标,ORACLE公司提出了Java虚拟机的概念,并将其应用于多个系统平台,取得了巨大成功。

Java语言这种应用只需经过一次编译,就可在任意软硬件平台中运行的特质,十分符合现今电信智能卡应用的开发需求。众所周知,传统的电信智能卡应用多为电信增值业务,数量少,功能简单,由智能卡制造商根据自身产品的硬件及卡片操作系统(COS)以Native的形式开发实现。而由于各厂家的应用互不兼容,无法交互使用,因此一个应用需要所有卡片制造商都进行开发,消耗巨大的开发、调试及维护成本。而随着移动支付领域的迅猛发展,电信智能卡上需承载越来越多的第三方应用,这些应用需求多样,功能复杂,且多是由第三方(如银行等金融机构)开发,无法继续沿用传统电信智能卡应用的native开发模式,于是Java卡的出现完美的解决了这个问题。

2.2 Java Applet是如何在Java虚拟机上运行的?

基于Java卡环境下的应用开发模式与其他高级语言平台开发模式基本类似——应用的开发方只需考虑如何使应用更好的实现功能,带给用户更佳的使用体验,而不必关心应用将装载在哪个平台,该如何兼顾平台的特性。这些问题统统交由Java卡虚拟机来解决。因此在这种模式下,应用开发商只需要开发一版Java卡应用(以下称为Java Applet),就可以装载并运行在任意一张搭载了Java卡虚拟机的智能卡中。

每一个Java Applet从编写到最终在卡片中运行,需要经过以下几个步骤:

1.开发者通过特有的开发环境,使用Java语言编写源代码(.Java文件),

2.将Java源码编译成字节码(.class文件)。字节码是是一种Java体系特有的代码形式,它具有一定的可读性,并可被Java虚拟机解析并执行。

3.编译好的字节码需要与对应的exp文件配合,通过转换器转换为cap文件,

4.通过读卡器或者空中通道,下载到Java卡上,安装、实例化后才可正常使用。

5.当这个应用被选择执行时,卡片的Java运行时找到该应用的虚拟机机器码对应的存储位置,并将其载入Java虚拟机进行解析、执行。

在这整套复杂的过程中,有以下两个最重要的环境:

A.class文件被转换成.cap文件的过程

该过程主要是将class文件的字节码转换成可被Java卡虚拟机识别并执行的虚拟机代码。由于ORACLE公司在JCVM规范中明确规定了Java虚拟机所支持的所有指令集,因此这是一个标准的转换过程。

B.cap文件下载到卡片中的过程

该过程主要是卡商自身实现的Java运行时对cap的解析和转译过程,由于ORACLE公司仅在相关规范中只规定cap文件的内容,但未规定其在卡片内部的存储结构和格式,因此这是一个非标的处理过程,各智能卡制造商根据自身的Java卡设计情况将cap文件进行解析,并按各自Java卡实现所设计的数据结构,拆解、组合成私有格式,存储在卡上。

三、Java卡运算能效因素及优化方向

3.1影响Java卡运算能效的因素

Java卡是相对比较复杂的电信智能卡产品,其具备较强大的扩展性和灵活的移植性。它在传统的电信智能卡软、硬件环境基础上,增加一层Java卡虚拟机和Java运行环境,所有的Java应用需要加载到这套Java卡运行环境中才能正常运行。因此其运算性能受制于整套智能卡软、硬件条件。具体影响Java卡的运算能效的因素包括以下几点:

硬件层面:

CPU位数及主频

RAM容量

Flash读写速度

安全算法协处理器支持情况

软件层面:

Java虚拟机性能

Applet源码性能

3.2探寻Java卡性能优化的方向

一张Java卡产品的运算能效受其软、硬件环境的共同影响,理论上,更换更强大的卡片硬件环境,如更快cpu,更大的RAM空间或读写更快的Flash存储器等,都可从根本上提升该Java卡的运算能效。同时,对Java apple源代码的编写方式进行优化,使用更科学的算法设计方法,优化代码执行流程,更有规律性、计划性的执行高耗时操作等,也可直接提升该应用的执行效率。

但从市场化的角度考虑,智能卡制造商对产品的硬件选型需要考虑成本和利润,在一定时期内,其使用在Java卡产品平台上的电信智能卡芯片硬件性能是固定的。而另一方面,Java卡相较传统native电信智能卡,其设计初衷就是提高产品的开放性和开发效率,将卡片实体生产者和卡片应用开发者分离,因此在Java卡环境下Applet的开发必然是离散形式,开发方并不可控,因此从Applet的编写角度提出优化要求并不可行。因此,Java卡产品性能可行、有效且可控的优化方向,应该是从电信智能卡制造商的Java虚拟机执行效能优化提升方向入手。

四、Java卡虚拟机的优化分析

4.1 Java虚拟机执行Applet原理解析

研究如何优化Java卡虚拟机的执行效率,就需要弄清Java卡虚拟机是如何解析执行Java Applet的。我们以下面这个简单的“HelloWorld”应用作为示例,解析Java虚拟机如何执行这个Applet的,以下是“HelloWorld”应用的源代码:

package com.HelloWorld;

import Javacard.fRAMework.*;

public class HelloWorld extends Applet

{

public static byte[] echoBytes;

private static final short LENGTH_ECHO_BYTES = 256;

public static final byte[] HelloWorld = {

//hello world

(byte)0x80, (byte)0x48, (byte)0x65, (byte)0x6c,(byte)0x6c, (byte)0x6f, (byte)0x57, (byte)0x6f, (byte)0x72,(byte)0x6c, (byte)0x64, (byte)0x53, (byte)0x05

};

protected HelloWorld()

{

echoBytes = new byte[LENGTH_ECHO_BYTES];

register();

}

public static void install(byte[] bArray, short bOffset, byte bLength)

{

new HelloWorld();

}

public void process(APDU apdu)

{

byte buffer[] = apdu.getBuffer();

Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, HelloWorld, (short)0, (short)HelloWorld.length);

apdu.setOutgoingAndSend( (short) ISO7816.OFFSET_ CDATA, (short)HelloWorld.length );

}

}

这是一个最简单的Java应用,主要实现的作用是:当该应用被选择执行后,应用向卡外设备返回“HelloWorld”字符串。

这个Applet的源码被编译并转换后生成了该应用的cap文件,cap文件是一种压缩格式文件,对其解压缩后,可以看到它是由以下12个组件(component)组成:

每个组件都是一个表单格式的数据结构,各自描述了这个Java应用的某方面信息,我们可以通过读卡器或空中下载通道,将这个cap下载到卡片中,并安装执行。

下载到Java卡后,当该Applet被执行时,Java虚拟机会执行以下操作:

A.读取cap文件中的Method.cap,下面为cap文件的内容:

01 bit[4] flags bit[4] max_stack

10 bit[4] nargs bit[4] max_locals

18 8C 00 00 11 01 00 90 0B 7F 00 01 18 8B 00 02 7A 02 30 8F 00 03 3D 8C 00 04 3B 7A 05 21 19 8B 00 05 2D 1A 08 7B 00 06 03 7B 00 06 92 8D 00 07 3B 19 08 7B 00 06 92 8B 00 08 7A bytecodes

B.载入其中的process函数的字节码并执行。

如下为Method comm Component中process函数的字节码:

05 21 19 8B 00 05 2D 1A 08 7B 00 06 03 7B 00 06 92 8D 00 07 3B 19 08 7B 00 06 92 8B 00 08 7A

这些字节码实际是一条一条Java虚拟机指令,具体的字节码分析如下:

05 flags: maxStack=5

21 nargs=2 maxLocals=1

19 aload_1

8B 00 05 invokevirtual

2D astore_2

1A aload_2

08 sconst_5

7B 00 06 getstatic_a

03 sconst_0

7B 00 06 getstatic_a

92 arraylength

8D 00 07 invokestatic

3B pop

19 aload_1

08 sconst_5

7B 00 06 getstatic_a

92 arraylength

8B 00 08 invokevirtual

7A return

对这些字节码进行解析可以发现,在执行这个process函数时,大致的过程主要是通过getstatic_a获取数据,并通过astore_2、aload_2、sconst_5等堆栈操作函数将数据放入堆栈,在通过invokestatic函数的调用相应的方法对堆栈中的数据进行运算,最后通过pop将结果弹栈输出结果,

对于堆栈的操作,由于缓存数据在芯片的RAM中操作,RAM的读写速度远远大于Flash存储域,因此上述操作中涉及堆栈操作的执行速度会很快,对Java卡虚拟机执行效率的影响也微乎其微。

而对于获取数据和调用方法的操作,则需要根据其指令后面的参数进一步执行相关操纵,例如getstatic_a类和invokestatic类指令:

7B (getstatic_a函数) 00 06(参数)

8D (invokestatic函数)00 07(参数)

这两个指令的参数实际标示了该信息在Constant Pool(以下简称CP) Component中的位置。CP中主要存储了整个Applet中所有类、方法和域的入口信息。对”Hello World”应用的CP内容进行解析如下:

05 tag

00 26 size

00 09 count

06 80 03 00 cp_info1

05 00 00 02 cp_info2

03 80 03 01 cp_info3

01 00 02 00 cp_info4

06 00 00 01 cp_info5

03 80 0A 01 cp_info6

05 00 00 00 cp_info7

06 80 10 02 cp_info8

03 80 0A 08 cp_info9

其中每条cp_info是一个常量成员的内容信息,其结构如下:

cp_info{

u1 tag

u1 info(3)}

由于Get类和invoke类指令的参数的结构不同,因此这两种函数在CP中获取进一步常量信息的流程也不相同,下面从”Hello World”应用的process函数中分别挑选一个Get类和一个invoke类函数,阐述一下各自的执行流程:

4.1.1 getstatic_a指令

7B 00 06 getstatic_a

00 06表现需要获取的静态数据在cp中的位置为“0006”,由于cp中成员index从0000开始,因此此处表示需要获取cp_info7的内容“05 00 00 00”。其中“05”表示这是一个“CONSTANT_StaticFieldref”,即后续的内容在Static Field Component中,其后面第一个”00 ”表示这是一个内部地址,后二个“00”表示其在Static Field Component中的index。对”Hello World”应用的Static Field Component内容进行解析如下:

08 tag

00 1A size

00 04 image_size

00 02 reference_count

00 01 array_init_count

03 type

00 0D count

80 48 65 6C 6C 6F 57 6F 72 6C 64 53 05 要读取的数据

00 00 default_value_count

00 00 non_default_value_count

这里根据cp中的入口地址找到,需要调用的常量内容“80 48 65 6C 6C 6F 57 6F 72 6C 64 53 05”,即”Hello World”这段字符Unicode码的数值。

至此可以看到,根据method component中解析出需执行的虚拟机指令getstatic_a及其参数(7B 00 06),最终找到了需要获取的常量数据。

4.1.2 invokestatic指令

8D 00 07(invokestatic)

00 07表示需要获取的静态数据在cp中的位置为“0007”,即cp_info8的内容“06 80 10 02 ”,其中“06”表示这是一个“CONSTANT_StaticMethodref”,“80”表示这个是一个外部的方法,且包的id是“00”,“10”表示这个方法是引用的class_token,而最后的“02”是这个方法的token。连贯起来理解,就这此处需要调用的方法是token为00的package下token为10的class中的“02”方法。

由于这条invoke指令调用的是一个外部的方法,所以需要到Import Component找到该方法归属的package aid,以下是”Hello World”应用的Import Component内容:

04 tag

00 0B size

01 count

04 minor_version

01 major_version

07 AID_length

A0 00 00 00 62 01 01 AID

由此可以获知该方法归属的包的aid为“A0 00 00 00 62 01 01”,即Javacard.fRAMework包,此时需要以此aid为检索条件寻找到该包的export文件,并在其中找到对应的方法、完成调用。

而如果调用的是一个静态方法,如invoke的参数在cp中的内容为06 00 00 01,则其中“06”为CONSTANT_ StaticMethodref的tag,第二个字节“00”表示这是引用一个内部的静态方法,需要到method component中引用该方法,最后两个字节“00 01”代表引用的方法在method component中的偏移地址。此时Java卡虚拟机需要再次载入method component表单,并根据偏移位置载入相应的虚拟机机器码,完成方法调用。

4.2 Java卡虚拟机优化方法

从上面的分析来看,不管Java卡虚拟机在实现getstatic_ a指令或者invokestatic指令时,都需要载入、解析和检索cap文件中的多个数据表单。而Java虚拟机操作对象的类型不同,还支持其他的getxxx和invokexxx指令,他们的执行流程与getstatic_a指令和invokestatic指令相同。我们可以将这些指令抽象成GET类指令和invoke类指令,并统一进行优化。

回顾上述这两类指令执行过程的分析:

Get类指令的实现过程需要分别解析Method Component 、Constant Pool Component和Static Field Component三张数据表单。

而invoke指令的执行过程更加复杂,如果是调用的是内部方法,则需要解析Method Component (2次)和Constant Pool Component,而如果要调用的是外部方法,还需要多解析一次Import Component以及调用的包内的Method Component 和Constant Pool Component。

上述所有的component的内容都存储在Flash存储单元中,读取和解析工作需要多次的Flash读取和擦写操作,虽然单纯从一条虚拟机指令的执行过程中来看,耗时还可接受,但在整个Applet的执行过程中,会出现上述成千上万次的虚拟机指令的执行,以及与之对应的、且成倍增加的Flash读写过程,如此积沙成塔,必然会拖累Applet整体的执行速度。因此我们认为针对此类API优化方法的基本思路是:

减少API执行阶段对多个component的读取和解析操作,以实现降低执行过程中Flash存储器读写操作频率的目的。

为了实现这样的目标,我们建议:

将原本在Applet执行阶段的component解析操作,提前到Applet的其他生命周期,并将解析的结果——被引用成员变量或方法的绝对地址作为参数放置method component的Get和invoke指令后面。

这样在Applet执行的过程中,每次执行此类函数的速度将大大提高。

下面的问题是:什么时间点才是执行绝对地址解析的最佳触发点?这个触发点应该符合以下2个条件:

1.在整个Applet的生命周期中执行频率相对最低;

2.在这个时间点执行绝对地址解析对用户的感知影响最小。

综合考虑上述两个条件,我们建议在Applet下载到卡片后,进行实例化的过程中进行绝对地址的解析操作。由于实例化操作在Applet的整体生命周期中仅会执行一次,同时,该执行过程一般是由应用的安装方发起,对最终用的使用感知基本没有影响。

通过上述方法对Java虚拟机字节码的执行方法进行优化,以实现Java虚拟机对Java API执行效率的提升。

五、结论

通过前文的论述,我们不难发现,由于受制于现有Java卡硬件水平的限制,不可能将所有的数据放置在RAM中操作,所以大部分数据仍只能储存在Flash存储器中,而Flash存储器的操作速度要远远低于RAM存储器,因此在一定硬件条件基础下,影响Java API的执行效率的关键因素就是——Flash存储器读写速度慢这一Java卡的硬件瓶颈,而这也是我们对Java卡虚拟机能效优化的主要着力点。由此我们可以总结出优化Java卡虚拟机运行能效,提高Java应用执行速度的根本方法——减少API执行阶段对多个component的读取和解析操作,将解析操作提前到Applet实例化阶段,并以被引用成员变量或方法的绝对地址作为参数放置method component的Get和invoke指令后面进行解析执行。

参 考 文 献

[1]《Virtual Machine Specification Version 3.0.1 Classic Edition》

[2]《Runtime Environment Specification Version 3.0.1 Classic Edition》

[3]《Inside the Java Virtual Machine, Second Edition》

[4]《深入理解Java虚拟机——JVM高级特性与最佳实践》

猜你喜欢
虚拟机优化
超限高层建筑结构设计与优化思考
民用建筑防烟排烟设计优化探讨
关于优化消防安全告知承诺的一些思考
一道优化题的几何解法
由“形”启“数”优化运算——以2021年解析几何高考题为例
虚拟机技术在计算机组装课堂中的应用
虚拟机服务器在教学实践中的探索研究
浅谈计算机系统虚拟化网络设置方案
任务驱动教学法在《网络应用服务管理》教学中的应用
虚拟机局域网组建技术应用初探