This appendix covers common optimization techniques when working with Java ME Embedded devices. Many of these techniques are common to the CLDC VM in both Java Embedded and traditional Java ME.
Embedded systems are typically designed to perform a specific task, unlike a general-purpose computer that strives to handle multiple tasks with equal efficiency. Some embedded systems also have real-time performance constraints for safety and usability; others may have little or no performance requirements, allowing the system hardware to be simplified to reduce costs. As such, developers should use the simplest application design possible to avoid overtaxing the embedded system.
Resources are frequently limited in embedded devices. Often memory is the most valuable resource. Many embedded devices have memory that is measured in megabytes (MB), and some of it is used by the Runtime Operating System (RTOS), leaving the remainder for use by the Java VM and its applications.
Be aware of how much memory is typically used by your application, the RTOS, and the Java VM. This will vary from one embedded board to another. By the time a Java ME embedded application exhausts all memory and is subject to an OutOfMemoryError
, there are few options left: the application must either force the VM to free any unnecessary memory using a System.gc()
call, or if that doesn't work, crash.
Java threads are often an expensive resource with embedded Java VMs. Java embedded applications work best when using minimal application threads. If you must create multi-threaded code, be sure to minimize the use of synchronized code, which can be expensive on embedded devices. As a general rule, avoid using the Timer
class, as an extra thread is created for each timer.
A common technique for creating Java ME embedded applications is to create a background thread in the startApp()
method of the MIDlet
class and reuse it throughout the IMlet lifecycle.
The Record Management System (RMS) is an I/O resource that should be used carefully. With any application that uses RecordStore
objects, opening and closing operations should be minimized. In addition, strive to group reads and writes in one section of code as much as possible. Spreading RecordStore
read and writes across the application can slow down the application.
Another common strategy when working with RecordStore
objects is to use buffers, which reside in memory and are often faster. This is a common technique:
For reading record stores, read the entire record into a buffer, then parse the buffer.
For writing record stores, write to a single buffer, then write the buffer to a record.
Here are some other general hints for optimizing your code that are pervasive throughout the industry for Java ME code:
Object creation is very costly with respect to memory and performance overhead. Create objects only when needed, and reuse any object instances that are created in a cache.
Use lazy instantiation if appropriate. However, many Java ME developers will create all objects outside the main loop of the program and reuse them as the application runs. With reusable objects, be sure to include a method that returns them to the original state, independent of the object constructor.
Avoid auto-boxing when possible.
Do not perform assertions in tight loops.
Avoid using variable-length arguments (varargs).
Use local variables instead of global variables when you can. Local variables are faster and generate less bytecode.
Only include system classes that you need. Avoid using wild character imports like import java.util.*
Instead, import classes directly, such as import java.util.Date
.
Don't perform string concatenations using the "+" operator. Use the StringBuffer
class instead. For example, don't do the following:
String str = new String ("Hello ");str += "World";
Instead, do this:
StringBuffer str = new StringBuffer ("Hello ");str.append("World");
Remember that in Java, String
objects are immutable, so performing concatenation with the "+" operator will in fact create a StringBuffer
, copy the contents of the String
over, append the other String
, then copy the result back into a different immutable String
object.
Divide your multi-dimensional arrays into single-dimensional arrays. Multidimensional arrays take more time to calculate the proper index in memory.
Avoid any unnecessary creation and disposal of objects and variables inside loops. For example, avoid a construct like this:
for (int i = 0; i < length; i++) { MyConstantClass c = new MyConstantClass(); results[i] = c.doSomething();}
Use a switch-case
construct instead of if
blocks, as they are compiled into more optimized bytecode. Remember that starting with Java 8, the switch
keyword can handle strings, which is more efficient that creating a large number of if
blocks that test using the equals()
method.
Use public variables directly instead of using get/set accessors.
Set variables to null
when you don't need them anymore to assist with garbage collection.
Aside from minimizing the number of classes in your application, developers can also make use of obfuscator tools, which are present in NetBeans and other IDEss. The original purpose of an obfuscator is to make reverse engineering bytecode more difficult. However, it can also create smaller and often faster class files. In fact, obfuscators typically reduce Java ME embedded class file size by 25% to 35%.
The NetBeans IDE contains an option to install the ProGuard obfuscator library. You can choose this option by right-clicking on your project and bringing up the Project Properties. Next, expand the Build leaf and select Obfuscating. If ProGaurd is not already installed, press the button to download and install the NetBeans module, as shown in Figure A-1.
Figure A-1 Installing the ProGuard Obfuscator Library
Once the obfuscator is installed, choose an obfuscation level by moving the slider anywhere from Level 1 and Level 9. As shown in Figure A-2, each level presents a detailed description in the window below that shows what operations the obfuscator is performing.