Slimming Wireless Java Applications
为无线Java应用程序瘦身
by Qusay H. Mahmoud作者 Qusay H. Mahmoud
October 2003
2003年10月
In the old days programmers wrote their programs in assembly language, and were able to pack a lot of functionality into a few kilobytes of memory. Since then, technology advances such as object-oriented programming (OOP) have taken developers a long way from machine-level instructions and direct management of memory. The fundamental goal of OOP in particular is to increase the productivity of programmers through code reuse (inheritance for example), and not to produce applications that are compact.
过去程序员用汇编语言编写程序,那时他们可以把许多功能写到仅有的几K内存中。从那以后,诸如面向对象编程(OOP)这样的技术进步使得开发人员远离了机器指令和对内存的直接管理。OOP所专注的基本目标是通过代码重用(比如继承)来提高程序员的生产力,而并不是用于产生紧凑的应用程序。
Following the advice to "optimize later," most programmers focus on the logic, correctness, and reliability of applications, not their size and speed. Developers of applications that run on mobile devices with low power and a small memory footprint, however, can't afford to ignore efficiency. Hardware is expensive in the wireless world. Devices with less memory are much cheaper than devices with the same characteristics that have more memory, and they consume less power.
遵循“优化最后”原则,大多数程序员把注意力集中在应用程序的逻辑、正确性和可靠性上,而不是它们的尺寸和速度。然而那些移动设备仅有很低的电力和很少的内存量,它们的应用程序开发人员不可能忽略运行效率的重要性。在无线的世界中硬件相当昂贵。较少内存的设备比那些特征相同而内存较大的设备便宜得多,而且它们消耗的电力更少。
This article starts by describing reasons for reducing the size of wireless Java applications, discusses problems that arise out of using class libraries, then presents some useful techniques for reducing application size without sacrificing quality.
这篇文章从解释缩小无线Javay应用程序尺寸的理由开始,讨论了使用类库而引发的问题,然后提供了一些有用的技术在不牺牲质量的前提下尽量缩小无线Javay应用程序的尺寸
Motivation
动机
Those who write software for big machines (and even notebook computers are huge compared to mobile devices) don't have to worry about memory, but developers of wireless Java applications must squeeze them into mobile devices with limited memory and compact processors. Making wireless applications smaller results in a number of benefits:
那些为大机器(甚至笔记本电脑和移动设备相比也属于巨大的)写软件的人不必担心内存问题,但是无线应用程序的开发人员必须把软件塞进只有有限内存和紧凑处理器的移动设备中。把无线应用程序写得更小有一大串好处:
Smaller applications download faster, and thus more cheaply.
They take less time to load and run; acceptable start-up time is crucial.
The smaller each application is, the more applications users can store on their devices.
Large programs run up against preset size limits on some Java-enabled mobile devices. For example, some phones have a maximum size of 64K for an application JAR file and 512K total storage for all applications.
较小的应用程序下载更快,因此更加便宜。
装载和运行的时间更短;可接受的启动时间非常重要。
每个应用程序越小,用户就能在设备上储存越多的应用程序。
在一些Java移动设备上,大的程序会超出尺寸限制。例如,有些手机对一个应用程序的JAR文件的最大尺寸限制为64K,而对所有应用程序的存储总合限制为512K。 Effects of Using Class Libraries
使用类库的影响
Class libraries are meant to increase the productivity of software developers and the reliability of their code. They have some disadvantages, however.
类库意味着提升软件开发人员的生产力和他们代码的可靠性。然而它们还是有一些缺点。
One is that class libraries may contain many more features than an application requires, increasing its size unnecessarily.
首先类库可能包含应用程序不需要的功能,这增加了不必要的文件尺寸。
You can use extraction tools to slim down applications that depend on class libraries. Such tools read the application's class files and the libraries they depend on to determine which classes, methods, and fields are actually used. They remove unused classes, methods, and fields, so the JAR file you produce contains only the functionality your application actually needs. Extraction tools have been known to reduce an app to less than 40% of its original size.
你可以使用提取工具来缩小依赖类库的应用程序。这种工具会读取应用程序的类文件以及它们所依赖的库文件,从而决定哪个类、方法和字段是实际被使用到的。它们会删除没有用到的类、方法和字段,这样最终生成的JAR文件仅包含应用程序实际需要的功能。据知提取工具能够把应用程序缩小至不到原始尺寸的40%。
Another disadvantage of class libraries is that ones the application depends on may not be available in the deployment environment (on a mobile phone for example). This problem can be dealt with in two ways each of which poses other problems:
类库的另一个缺点是,应用程序所依赖的一些类库可能在发布环境中(比如在一个手机上)并不可用。这个问题可以用两种办法解决,但是每种方法都会造成其他问题:
The user can download and install the needed library, but this process is complex and error-prone; for example, users can easily install the wrong version of a library. While a proper packaging mechanism can handle versioning and dependency checking, the process is likely to require multiple transactions, and an error late in the process might invalidate earlier results if, for example, the device runs out of space before all dependencies are resolved.
The developers can ship the class library that the application depends on an XML parser, for example with the code. This approach, however, increases application size, the very result they're trying to avoid.
用户可以下载并安装所需的库,但是这个过程既复杂又易出错;例如,用户很容易安装了错误的库版本。即便有适当的打包机制来处理版本和检查依赖性,这个过程还是需要多步处理,一个稍后产生的错误可能推翻先前的结果;例如,在解决完所有依赖性之前设备空间不足了。
开发人员可以把应用程序依赖的类库装载到一个XML解析器中,甚至把源代码也装入。然而这种方法增加了应用程序的尺寸,这恰恰是他们要避免的结果。
Class libraries also raise security issues. For example, JAR files from different vendors will have different signatures. This problem is one of the reasons shared libraries were not added in MIDP 2.0.
类库还会带来安全性问题。例如,来自不同供应商的JAR文件会带有不同的签名。这个问题也是为何MIDO 2.0中没有共享库的原因。
Techniques for Manual Reduction
手工缩减的技术
There are many ways to reduce the size of your wireless Java application.
有很多方法可以缩小无线应用程序的尺寸
Keep it simple. Simplicity of design can yield robustness, speed, and efficient use of space.
保持简单。设计的简单性带来健壮性、速度和空间使用效率。
Recompute rather than store. Recomputing a value rather than storing it for later retrieval can save space. As an example consider the following code fragment:
重新计算比存储好。重新计算一个值而不是存储后检索,这可以节约空间。作为一个例子请考虑以下代码片断: ...
String s = "HelloMyDearFriend";
int len = s.length();
for(int i=0;i<len;i++) {
// do something with s
}
...
This code uses the String.length() method to compute the length of the string before the loop, and thus only once. This approach is fine from a performance point of view, but creation of the len variable does increase the application's use of memory. To eliminate the variable you can recompute the string length on each loop iteration, as in the following rewrite:
这段代码使用了String.length()方法在循环前计算了字符串的长度,这样只需计算一次。这种方式从性能的角度来看不错,但是创建len变量的动作确实增加了应用程序消耗的内存。你可以在每个循环叠带中重新计算字符串长度以消除这个变量,重写代码如下: ...
String s = "HelloMyDearFriend";
for(int i=0;i<s.length();i++) {
System.out.println(s);
}
...
If you compile both versions, you'll find that the one that recomputes the length instead of storing it is smaller in size by several bytes. These bytes can add up....
如果你编译这两个版本,你会发现重新计算长度的版本比存储它的版本小几个字节。类似这样节约下的字节如果累计起来……
Choose an elegant data structure to store information. You should always consider your options for a data structure to store your information on the device.
选择一种简洁的数据结构来存储信息。你应当慎重选择用于在设备上存储信息的数据结构。
Note: It is all about trade-offs. Sometimes you must trade functionality or performance to gain space. You should always study the alternative before making a decision.
注意:这完全是关于代价和平衡的问题。有时你必须付出功能或性能的代价来交换空间。在作出决定前,你应当研究各种备选方案。
Remove unnecessary classes. One way to "keep it simple" is to review your application design and eliminate classes that are not actually needed.
删除不必要的类。“保持简单”的一条途径就是回顾你的设计并删除那些并不真正有用的类。
Reduce functionality and features. Is all the functionality you've planned actually needed?
减少功能和特性。你计划中的所有功能都是必须的吗?
Avoid inner classes. Many programmers use inner classes, to implement event listeners for example. Convenient, but behind the scenes the compiler generates special instructions to allow the inner class to access the outer class's private data. Consider the following snippet of code for an event listener implemented as an anonymous inner class:
避免使用内部类。许多程序员使用内部类来实现例如事件侦听这样的任务,但是在这种情形下,编译器会生成特殊的指令以允许内部类访问其外部类的私有数据。考虑以下代码片断,它是一个用匿名内部类实现的事件侦听器: public class MyMIDlet extends MIDlet {
// code for the MIDlet
// ...
// Anonymous inner class
setCommandListener(new CommandListener() {
public void commandAction(Command c, Displayable d) {
// implementation;
// may access MyMIDlet's private fields
}
});
}
Redefining the inner class to be a separate helper class would eliminate the extra overhead.
把内部类重新定义成单独的帮助类就能够去除额外的开销。
Avoid deep inheritance hierarchies. Although the benefits of inheritance (such as software reuse, increased reliability, and code sharing) are great, nothing is without cost of some sort, including inheritance. Programs must use memory and time to construct fields inherited from a base class even when the derived class makes no use of them. When a method is invoked and it is not part of the derived class, its immediate superclass will be searched; if that class doesn't define the method, its superclass will be searched, and so on. All that extra message passing adds bulk to the bytecode and steals processor time. If a class doesn't use most of the resources it inherits from the base class, it's better to reimplement the resources it does use, instead of inheriting them.
避免深度继承层次。虽然继承的好处(比如软件重用、提高可靠性、代码分享等)很大,但任何事物都是有某种代价的,包括继承。程序必须花费内存和时间来构造从基类继承的域,即使导出类根本不使用它们。当一个方法被调用并且它不属于导出类时,它的直接超类将被搜索;如果那个类也没有定义这个方法,它的超类将被搜索,依此类推。所有在此过程中传递的消息都会添加字节码的数量,并盗走处理器的事件。如果一个类并不使用它从基类继承的大部分资源,则最好重新实现它用到的资源,而不要继承它们。
Avoid using inheritance when it isn't necessary. For example, if you wish to use multiple threads, implement the Runnable interface rather than extending the Thread class.
非必要不使用继承。举例来说,如果你希望使用多线程,实现Runnable接口比扩展Thread类好。
Use short names for classes, methods, and fields. Savings from abbreviating any one name aren't dramatic, but they add up. An automated way to get shorter names is to use obfuscation tools. Although their primary intent is to make it harder for a competitor to reverse-engineer your application by decompiling your .class files into easily read .java files, obfuscators also reduce code size by shortening names. There are several obfuscators available, but you may want to try RetroGuard, which is available for free. You can use this obfuscator from the J2ME Wireless Toolkit.
对类、方法和字段都使用较短的名字。任何单个名字的缩写都不可能带来戏剧性的节省,但是它们可以累积起来。自动获取短名字的办法就是使用混淆工具。混淆工具使用缩短的名字从而可以减少代码长度,虽然它们的主要目的是增加竞争者对你的应用程序实施反向工程的难度,他们可能把你的.class文件反编译成容易阅读的.java文件。有不少可用的混淆器,但是你可以试一下免费的RetroGuard。你可以从J2ME Wireless Toolkit中使用这个混淆器。
Tip: In the build cycle, always perform obfuscation before preverification. The preverify tool, which is shipped with the J2ME Wireless Toolkit, preprocesses classes for use by the KVM without changing class names.
提示:在编译周期中,应当在预校验之前进行混淆。预校验工具和J2ME无线工具包一起提供,它对KVM使用的类进行预处理而不改变类名。
Preverification is itself a way to reduce demand on the limited resources of mobile devices. In the J2SE Java virtual machine, the class verifier is responsible for rejecting invalid class files. A Java virtual machine supporting the CLDC must be able to reject invalid class files as well, but the class verification process is expensive and time-consuming, not ideal for small, resource-constrained devices. The designers of the J2ME virtual machine lightened the load on the mobile device by moving most of the verification work onto the developer's desktop, where the class files are compiled, or onto a server machine, where applications are downloaded. This preliminary, off-device verification leaves the device responsible only for running a few checks on the class files, to ensure that they have in fact been verified, and that they're still valid.
预校验本身就是一种对移动设备的有限资源减少需求的办法。在J2SE虚拟机中,类校验器负责剔除非法的类文件。支持CLDC的Java虚拟机必须也能够剔除非法的类文件,但是类校验非常昂贵而耗时,对于小的资源紧缺的设备来说并不理想。J2ME虚拟机的设计者减轻了移动设备上的负荷,把大部分校验工作搬到的开发者的桌面上,那是类文件被编译的地方,或者搬到用于下载应用的服务器上。在这个初步的脱机校验后,设备仍需要运行一些剩余的检验工作已确认类文件实际确被教研过,而且依然是合法的。
Take advantage of resource-processing tools that reduce PNG resolution and color depth. Many devices are capable of displaying only a few colors per pixel, so creating images with many colors per pixel may increase your app's memory footprint without adding any value to it. You may also want to reduce the dimensions of the image.
利用资源处理工具减少PNG的分辨率和色深。许多设备在每个像素上仅能够显示有限的颜色,所以创建每个像素上颜色太多的图像可能增加应用程序的内存占用量,却不带来任何价值。你可能还得减少图像的尺寸。
Don't include unnecessary images. Why use memory for images that don't enhance your application?
不要包含非必须的图像。为什么要用那些对应用程序没有好处的图像呢?
Eliminate access methods. Get and set methods can cause your application to run more slowly than if it accessed member variables directly, and they increase program size. Once again, though, it is all about trade-offs.
去除访问方法。和直接访问成员变量相比,Get和Set方法可能导致应用程序运行较慢,而且他们会增加程序尺寸。再重复一遍,这完全是关于代价和平衡的问题。
Refactor. As applications evolve over time, they often become less space-efficient. Sometimes the best way to save space is to refactor the application, with space-efficiency as a high-level design goal.
重构。应用程序随着时间演化,它们常常变得不够紧凑。有时要节省空间的最好办法就是重构整个应用程序,把空间紧凑作为一个高级的设计目标。
Minimize object creation. Object creation consumes memory and processor cycles, and leads to object destruction, which further reduces performance. Look for ways to reuse existing objects rather than create new ones.
尽量减少创建的对象。创建对象会消耗内存和处理器周期,而且导致更加影响性能的对象销毁动作。请设法重用当前存在的对象,而不是创建新对象。
As one example, instead of creating a return object inside a method, consider passing to it a reference to an object that already exists in the calling code, and modify that object's values.
举个例子,不要在一个方法中创建一个返回对象,而是向这个方法传递在调用者代码中已存在的对象的引用,然后修改那个对象的值。
For another example, consider this code snippet:
另一个例子,考虑以下代码片段: ...
int len = record.length;
try {
for(int i=0; i<len; i++) {
MyObject obj = new MyObject();
// do something with obj
}
} catch(Exception e) {
e.printStackTrace();
}
...
This code creates and destroys a new instance of MyObject every time the loop iterates. You can avoid this object churning by moving the object creation outside the loop. A more efficient way to rewrite the code above would be to create the object outside the try block and reuse it as follows:
这段代码在每次循环叠带中都创建和销毁一个MyObject的新实例。你可以把创建对象动作移到循环体外来防止这种对象“搅拌”现象。以下是一种更有效率的写法,它在上述的try代码块外创建对象并重用它。 ...
int len = record.length;
MyObject obj = new MyObject();
try {
for(int i=0; i<len; i++) {
// do something with obj
}
} catch(Exception e) {
e.printStackTrace();
}
...
By reusing a single object instead of creating many, the program uses less memory and reduces garbage collection. To avoid memory leaks, however, you should clear all the elements in any recycled object before reusing it. To do that, just add a clear() method (as in the Collection interface in J2SE) or a reset() method.
通过重用单个对象而不是创建许多个,程序将使用更少的内存并减少垃圾回收的次数。为了防止内存泄漏,你应当在重用前清除任何被回收对象的所有元素。要做到这一点只需添加一个clear()方法(就像J2SE的Collection接口)或一个reset()方法。
Avoid string concatenation. It is well known that concatenating objects with the + operator causes object creation and subsequent garbage collection, and thus chews up both memory and processor time. It is more efficient to use StringBuffer. Note, however, that merely switching from concatenation to use of StringBuffers is not always sufficient because of the way arrays are reallocated. Inefficiencies arise, chiefly because the default character buffer for StringBuffer is 16, and when the buffer is full a new one (usually twice the size of the original) must be allocated. Once the content is copied to the new one, the old buffer is released. This frequent reallocation can be avoided if you create a StringBuffer big enough to hold the longest string you're likely to store.
不使用字符串串连。众所周知用 + 操作符串连对象会导致对象创建和随之而来的垃圾回收,而这样会消耗内存和处理器时间。更有效率的办法是使用StringBuffer。注意,由于数组重新分配的方法,其实仅仅从使用串联转换到使用StringBuffers并不总是有效。低效率的起因,主要是由于StringBuffer的默认字符串缓冲是16,当缓冲满了以后必须分配一个新的缓冲(通常是原始尺寸的两倍)。一旦内容被复制到新的缓冲中,旧的那个就被释放了。如果你创建一个足够大的StringBuffer来存放将要存储了最长字符串,就能防止这种频繁的重新分配。
Conclusion
总结
Several vendors of Java-enabled mobile devices have imposed physical constraints on the size of each application and the total memory available for applications. Application size affects download and start-up times. This article presented several techniques for reducing the size of wireless Java applications.
一些Java移动设备的提供商对每个应用程序的尺寸和总可用内存作了强制的物理限制。应用程序尺寸会影响下载和启动时间。这篇文章展示了一些缩减无线Java应用程序尺寸的技术。
Due to the increasing complexity of wireless applications and time-to-market pressures, manual compaction can be difficult. Therefore there is a growing need for automated compaction techniques and other tools that will help developers reduce the size of their wireless Java applications.
迫于无线应用程序不断增长的复杂性和推向市场的时间压力,手工压缩是困难的。所以对自动压缩技术和其它帮助开发者缩减无线Java应用程序尺寸的工具,市场需求也正在不断增长。
For More Information
要查看更多信息
Acknowledgments
感谢
Special thanks to Gary Adams of Sun Microsystems, whose feedback helped me improve this article.
特别感谢Sun Microsystems的Gary Adams,他的反馈帮助我改进这篇文章。
About the Authors:
关于作者:
Qusay H. Mahmoud provides Java consulting and training services. He has published dozens of articles on Java, and is the author of Distributed Programming with Java (Manning Publications, 1999) and Learning Wireless Java (O'Reilly & Associates, 2002).
Qusay H. Mahmoud 提供Java咨询和培训服务。他出版了成打的关于Java的文章,而且是Distributed Programming with Java (Manning Publications, 1999)和Learning Wireless Java (O'Reilly & Associates, 2002)的作者。