Whether your programs will run quickly shouldn't be a gamble.
Do you
like to bet on horse racing? Do you like your chances with a horse that has
20-to-1 odds or 10-to-1 odds? The thing that makes horse racing so interesting
is that you don't know which horse is going to win the race. You may know which
horse is favored, but you really don't know which one will outperform all the
others on any given day.
Unfortunately, this is sometimes the case with
a Java application that runs on the iSeries. But you want your application to
run at top speed every time it runs. There should be no chance involved. The
odds of your Java application running at a high performance rate should always
be even--one to one.
A Java programmer new to the iSeries server simply
creates a Java program on a PC by compiling it and putting all of the class
files into a Java ARchive (JAR) file. The JAR file is transferred to a directory
on the iSeries server, and then the programmer goes into Qshell and enters the
cd command to the directory where the JAR file was transferred. Once in the
directory, the Java command is used to run the application. The RUNJVA CL
command is another way of starting a Java application. For example, I
created a simple Hello.java program that was compiled and put into a hello.jar
file. The hello.jar file was transferred into a directory called "test" on an
OS/400 V5R2 iSeries system. Instead of using the Qshell command, I used the
RUNJVA command. I entered the following on the iSeries command line:
RUNJVA CLASS(com.test.Hello) CLASSPATH('/test/hello.jar')
Once the RUNJVA command was run, it took only a couple of seconds for the
application to start and complete. Realize that the hello.jar file was only 707
bytes and had no dependency on other JAR files. Many times, applications use
class files from other JAR files. When this occurs, the other JAR files must be
added to the classpath.
After this, I thought it was pretty easy to run
Java applications on the iSeries server. I simply placed my class files into a
JAR and then used the RUNJVA command, pointing only to the main class file and
the JAR file that held all of my class files.
Then, I created another
application with a JAR file of 689,210 bytes. The application listened for XML
messages that were sent on a data queue, and then performed specific actions on
the server, depending on the message. This application was dependent on five
other JAR files that were in the IFS. I used the RUNJVA command, specifying my
main class file within the CLASS parameter and my dependent JAR files in the
CLASSPATH parameter:
RUNJVA CLASS(com.test..ApplicationMain) CLASSPATH('/test/app.jar:/test/xercesImpl.jar: /test/xmlParserAPis.jar: /qibm/proddata/os400/jt400/lib/jt400Native.jar: /qibm/proddata/os400/able/able.jar: /qibm/proddata/os400/able/antlr.jar: /qibm/proddata/os400/able/Jlog.jar')
Unfortunately, when running this Java application, the program took about
one minute to start. Unacceptable! And after the application started, it took
even longer to complete. I found a number of ways to speed up Java applications
running on the iSeries server.
Warp Speed!
Certain parameters within the RUNJVA program can
improve application performance. The default values of *OPTIMIZE for the
interpret parameter and *JIT for the optimize parameter allow optimization to
occur while your Java application is running. The first time a Java
application is run using the default values, the classes in the JAR file must be
verified. The verification step can be fairly resource-expensive and
time-consuming, and this is why my application ran so slowly in the example
above. The result of the verification step in the internal form of the classes
is used by the JVM and is attached to the JAR file. Any subsequent time the
application is run, this pre-verified data is checked to see if it is current
with the class in the JAR file. If it is, the JVM will use this data, and the
application will run faster than it did the first time.
When you use the
*JIT parameter, statistics are kept for each method that's being called within
your Java application. Once the value for the Java system property,
os400.jit.mmi.threshold, is reached, optimization occurs only for that method.
The default and recommended value for the os400.jit.mmi.threshold is set to
2000. This optimizes only the methods that are called most often within your
Java application.
If your application uses a custom class loader, you
can use the os400.define.class.cache Java system property to improve the
start-up time of your application. Every time your application starts, classes
loaded by a custom class loader must be verified. Using the
os400.define.class.cashe property allows the verified classes to be saved and
used the next time the application is run. This is very similar to the mechanism
used by the default Java class loaders.
Many Java programmers create
their own custom class loaders. One reason is for security purposes. They want
their applications to load only the classes within their JAR files and don't
want to allow the capability of someone else loading a class file that might
corrupt their applications. Some Java programmers like the capability of
allowing their applications to add classes dynamically while the application is
running, so they implement their own custom class loader with additional
security. This enables programmers to add functionality to their applications
without shutting down the application.
The value of the
os400.define.class.cache.file should be the name of a valid JAR file. The JAR
file must contain a valid JAR directory, but it must have no other contents
except a single member within the JAR file. An example of the JAR file is
located in the /qibm/proddata/Java400 directory called QDefineClassCache.jar. I
created my custom class loader within my application and used the
os400.define.class.cache.file Java property, pointing to the
/qibm/proddata/Java400/QDefineClassCache.jar file:
RUNJVA CLASS(FTPServerSocket) PARM(os400.define.class.cache.file '/qibm/proddata/Java400/QDefineClassCache.jar') CLASSPATH('/garbersb/FtpServerSocket.jar') OPTIMIZE(*OPTIMIZE) INTERPRET(*JIT)
When I started the application for the first time with the
os400.define.class.cache.file Java property, it took the same amount of time it
would have if I hadn't specified the os400.define.class.cache.file Java
property. However, the second time I started the application, it started
quickly, thanks to my cached start-up classes.
At certain times, a Java
application may run more slowly than normal in certain spots. When this occurs,
many programmers examine their code and try to find out why. What's nice is that
you can collect performance information for your Java applications by specifying
the Java system property os400.enbpfrcol with a value of 1, 7, or any non-zero
value. A value of 1 collects all of the procedure entry and exit performance
data. A value of 7 collects procedure entry and exit data along with performance
data after calls to external procedures. A non-zero value generates Java entry
and exit data along with Java pre-call and post-call events. Once you enable
performance collection, use the performance tools to collect information about
your application. Many Java programmers gather this information to see how their
program is executing and what methods are used most often. Once the performance
data is collected, you can use the Performance Trace Data
Visualizer (PTDV) tool, which allows you to view the performance trace
information.
Another way to possibly increase performance is by using
the Create Java Program (CRTJVAPGM) command, which allows you to optimize the
JAR file, giving you choices of *INTERPRET, 10, 20, 30, or 40.
*INTERPRET pre-verifies the Java program and converts it to an internal
form while the application is running. The pre-verification takes some time,
which may cause your Java program to initially run slowly, depending on how big
your JAR file is. When you use *INTERPRET, the size of your JAR file remains the
same. Using 10, 20, 30 or 40 optimizes the iSeries server's machine
instructions. The following are detailed descriptions of the differences between
10, 20, 30 and 40. This information was taken from OS/400 help for the
CRTJVAPGM:
- 10--The Java program(s) will contain a compiled version of the
class file byte codes but will have only minimal additional compiler
optimization. Variables can be displayed and modified while debugging.
- 20--The Java program(s) will contain a compiled version of the class file
byte codes and will have some additional compiler optimization performed.
Variables can be displayed but not modified while debugging.
- 30--The Java program(s) will contain a compiled version of the class file
byte codes and will have more compiler optimization performed than optimization
level 20. During a debug session, user variables cannot be changed but can be
displayed. The presented values may not be the current values of the
variables.
- 40--The Java program(s) will contain a compiled version of the class file
byte codes and will perform more compiler optimization than optimization level
30. In addition, it includes optimization that disables call and instruction
tracing. Optimization level 40 includes cross-class optimizations. In a small
number of cases, the order in which static initializers are run for unrelated
classes (not related by inheritance or containment) may be different than
outlined in the static initialization specification. In addition, it includes
optimization that disables call and instruction tracing.
Specifying 10, 20, 30, or 40 for optimization increases your
applications' start-up performance. This however, also increases the allocated
size of your Java program. For example, I put a 703 KB JAR file on the iSeries
system. After optimizing the JAR file to 40, I saved the object to put on
another iSeries system in order to avoid having to enter the CRTJVAPGM command
again on the second iSeries system because it takes some time for the command to
process to completion. I first created a save file called FTPSERVER in library
GARBERSB. I then issued the following command to save my JAR file into a saved
file:
SAV DEV('/QSYS.LIB/GARBERSB.LIB/FTPSERVER.FILE') OBJ('/garbersb/FTPServerSocket.jar)
The FTPServerSocket.jar file within the saved file totaled 11,207 KB. As
you can see, optimization increased the size of the JAR file.
Once I
transferred the saved file onto the second system, I restored it in the same
directory by using the following command:
RST DEV('/QSYS.LIB/GARBERSB.LIB/FTPSERVER.FILE') OBJ('/garbersb/FTPServerSocket.jar)
After I ran that command, the FTPServerSocket.jar file was restored. When
I ran the Java application on the second system, it started right away, thanks
to the optimization.
You can also specify other JAR files that are
associated to this application within the classpath parameter, along with the Java
Developer Kit (JDK) version. This allows the JDK you're running, along with
other dependent JAR files, to function as if it is one bound application. Using
CRTJVAPGM should increase your application's start-up speed and improve its
overall performance.
I optimized the aforementioned JAR file specifying
the 1.4.1 JDK and a classpath pointing to five other JAR files upon which my
application was dependent. It took around 30 minutes for the CRTJVAPGM command
process. As you can see, to make your Java application run faster, you must
spend time up front optimizing your application for optimal
performance.
One other thing that's nice about the CRTJVAPGM command is
that you can specify adopt authority for your Java application. This allows the
owner of the JAR file that was put on the system to be the application's owner.
Simply specify *YES for the Use Adopt Authority parameter and *OWNER for the
User Profile parameter. Doing this adds security to your application because it
gives you the capability of running your Java application underneath a defined
user profile that doesn't have a password associated with it. (Note: When using
*JIT, this isn't possible.)
So, what are you supposed to use when
creating Java applications for optimal performance? Benchmark tests show that
applications using *JIT outperform applications using an optimization of 40 by
up to 20%. Using *JIT is also more efficient because only the most frequently
used methods will be turned into machine code. However, when you use *JIT,
starting your Java application will be a little slower than with optimization
levels 10, 20, 30, or 40. If your application uses a custom class loader,
specifying the os400.define.class.cache Java system property can significantly
improve the loading of the classes that it loads. If additional security is
required within your Java application, consider using the CRTJVAPGM command and
specifying adopt authority. It strictly depends on your application. By knowing
the different ways to optimize your Java application on the iSeries, there is no
need to worry about what your odds will be when it runs. They will always be one
to one.
Benjamin Garbers is a Software Engineer
within the Rochester Support Center. He is currently developing Internet
applications for IBM's iSeries
Technical Web site.
|