MC Press Online

Thursday, Feb 23rd

Last updateWed, 22 Feb 2017 3pm

You are here: Home ARTICLES Programming Programming - Other Java Make Your iSeries Java Applications Perform

Programming / Java

Make Your iSeries Java Applications Perform

Support MC Press - Visit Our Sponsors

NEW BOOK!

Flexible Input, Dazzling Output with IBM i


ORDER YOUR COPY

*******************

Click for this Month's

Bookstore Special Deals

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.

BLOG COMMENTS POWERED BY DISQUS