Battle of the JVMs
Client-side Java: How do Microsoft's and Sun's Java virtual machines compare in terms of compatibility and performance?
This month, Navneet Mathur offers the second half of his tour of the client-side problems he encountered while working on a large-scale Java project. He examines code compatibility between the Microsoft and Sun Java virtual machines (JVMs) and looks at their differences in terms of memory management and overall performance. He also offers several rules of thumb for avoiding problem areas while developing Java applications. (2,600 words)
As we learned last month, Java is a really neat technology, but to architect a Java-based solution you'll need to know a lot more than just the Java programming language. In designing an application, you must look at memory requirements and how your application is utilizing memory, production requirements related to performance and usability, and vendor/product choices, because all product implementations by vendors are not created equal! Some developers may believe that if you've selected the same tools and products for an application, you essentially have the same architecture, but this isn't so. Architecture reflects how the technologies are put together and how the applications are designed. This month, Navneet Mathur concludes his discussion of the hurdles he encountered while architecting a large-scale Java application.
ast month, I drew on my experience of working on a large-scale Java project to discuss issues related to Java's garbage collection scheme. This month, I'll pick up where I left off, with a discussion of the two most popular Java virtual machines (JVMs) from Microsoft and Sun. In addition, I'll cover performance issues with Java in general, and will attempt to provide some tried and tested solutions to these issues. If you did not read the September IT Architect, I strongly encourage you to do so before delving into this article.
Although I will focus exclusively on the JVMs from Microsoft and Sun here, the issues of memory management, code compatibility, and performance I will highlight are also applicable to other popular JVMs such as those from Symantec and IBM.
The recommendations contained in this article have been tried and tested on a large online transaction processing (OLTP) Java application that has now been in production for six months. We developed this application using the Java Development Kit (JDK) 1.1.x from Sun. We began our project before JDK 1.1 was released and started development as soon as JDK 1.1 was available. We stayed abreast of the latest JDK versions throughout, and the current production system is compiled on JDK 1.1.6. Initially we executed our code on the Sun JVM that came with the JDK. However, due to various reasons that I will highlight below, we also executed the code that was compiled using Sun's JDK 1.1.x on Microsoft's JVM.
The JVMs: Microsoft's vs. Sun's
The compatibility or lack thereof between the JVMs from Sun and Microsoft is the subject of many technology debates. For technology architects that would like to see the ultimate promise of Java come to fruition, JVM incompatibility is at odds with the write-once, run-everywhere architectural objective.
I will try to bring forward some issues that are common across the two JVMs and also discuss some compatibility and performance issues.
First, a note on installation: In order to completely install the Microsoft JVM, both the SDK for Java version 2.01 and Internet Explorer (IE) 4.0 must also be installed. This can make things difficult for those who wish to use the Microsoft JVM but who want to standardize on the Netscape browser.
In last month's article, I mentioned the lack of control over the timing of garbage collection. As more screens are opened in an application, more memory is needed. Because garbage memory isn't collected in a timely fashion, the JVM requests more memory to be allocated by the operating system (OS), thereby reducing the memory available to other applications. One would expect this to be a temporary problem; that as garbage-collected memory built to excess within the JVM it would be returned to the OS with a thank-you note. Well, to my amazement, I found that the JVMs don't return any of the memory grabbed from the OS. To the contrary, the memory grabbed by the JVM doesn't become available to the OS until the JVM is terminated.
The garbage collection problem is greatly magnified by this JVM issue. Because garbage memory isn't recycled fast enough, more memory must be allocated from the OS. This cycle continues as more screens are brought up within the Java application, thereby preventing the low-priority garbage collector thread from kicking in as much as desired. Over a period of time, depending on the pattern of usage, the OS runs out of RAM and virtual memory, which causes the other applications on the client to become unstable. Eventually, the entire client machine becomes unstable. This can lead to applications crashing or freezing, including the Java application itself.
The inability of the JVM to return memory to the OS is common to both the Microsoft and Sun JVMs. I suspect this is because the JVM spec isn't clear about memory being returned to the OS after being freed by the garbage collector. This issue has been documented on Sun's Web site as bug id 4065018. I encourage you to take a look and send in your comments so this issue can be tackled before the next release.
For now, I recommend a RAM size of 64 megabytes for client machines running a moderately sized OLTP Java-based application. Last month's article mentioned the ObjectWatcher class and the Debug button we built to track the garbage collection of object. Using this, we compared the two JVMs from Microsoft and Sun to see their memory management capabilities. After conducting many similar tests on both JVMs we found that the Microsoft JVM consistently freed up more garbage memory than the Sun JVM during the same timeframe. Over a short period of time, this is insignificant; however, if the Java application is used for an extended period of time the Microsoft JVM turns out to be more stable. Because of its better memory management capabilities, there were fewer crashes and fewer OutOfMemory exceptions running under the Microsoft JVM compared to the Sun JVM. This is clearly visible if you monitor the system memory on the Windows NT platform. It's even more pronounced on the Windows 95 platform if you watch the GDI resource meter.
Note that using the Microsoft JVM to run the code compiled on Sun's Java compiler can cause a null pointer exception to occur at random places when the user clicks on a screen in the background. This is because the getFocus event is triggered, causing a message to be passed to appropriate listeners. This notification message causes the exception. However, the exception doesn't seem to cause any adverse effects to the overall stability of the application.
The developer community has twisted Sun's "write once, run everywhere" slogan into "write once, test everywhere" and "write once, port everywhere." These phrases portray the developer community's apparent frustrations over code incompatibility between the two JVMs from Sun and Microsoft. The goal of the developers of the Java language and the JVM specs was to create 100 percent pure Java code that could run on any JVM with the same results.
Unfortunately, code that is certified 100% Pure Java (as per Sun's standards) doesn't always compile on the Microsoft Java compiler; nor does this code run as-is on the Microsoft JVM after being compiled with the Sun compiler. In fact, it's very difficult to figure out these code incompatibilities because neither are documented on Sun's or Microsoft's Web sites.
When the Microsoft JVM executes code compiled with the Sun compiler and comes across a code incompatibility, the JVM just freezes without any exception or error message displayed. In the beginning, it was difficult to figure out if this was due to a system problem or a code problem. Once we realized that this was due to code incompatibility, it was a challenge to determine which line in the code was causing it to happen. The only way to troubleshoot this is to localize the problem area by putting print statements in code and then adding debugging statements after each line in the suspect area. Of course, this can be time consuming. To minimize this effort, I highly recommend that all JVMs to be used by the target user community be installed on the developer's machine, and code units be tested on each JVM to determine compatibility issues up front. As an applications architect, I look to the industry to continue improving Java-related debugging mechanisms and tools.
A couple of compatibility issues that really spooked me are Sun's use of a border layout default and abstract private methods.
In Sun's version of the Java language, the center panel is the default for the border layout. However, the Microsoft JVM does not have a default panel for the border layout. Therefore, if this code is executed on the Microsoft JVM, contents on the screen seem to disappear until you realize the problem and rectify it by explicitly specifying the panel each time for the border layout.
Below is sample code explicitly stating where to add the panel, a recommended practice:
gbxGrid.setLayout(new BorderLayout(5, 5)); gbxGrid.add("Center", grdDisplay);
And here is code using the border layout default, which causes problems on the Microsoft JVM:
gbxGrid.setLayout(new BorderLayout(5, 5)); gbxGrid.add(grdDisplay);
In addition to the border layout incompatibility, the Microsoft JVM doesn't allow methods to be declared as abstract private, but the Sun JVM does. Use of abstract private methods does not logically make sense, and this may be something that Sun's compiler doesn't check stringently.
The look and feel of the Java application also varies depending on which JVM is used. Here are some of the differences that are visually apparent. The screen components used in our application were based on Sun's AWT (Abstract Windowing Toolkit).
We extensively tested our Java application on the two JVMs, both in-house and by going live with users using both JVMs simultaneously to get a "real" picture of stability and performance. As it relates to my application, we found that not only did the Microsoft JVM free up garbage objects relatively faster, but it also had better overall performance. Our applications were tested on the Windows NT 4.0 platform with 32 megabytes of memory. We measured performance in terms of time taken to invoke a screen and to execute saves. Although both JVMs are stable, the Microsoft JVM has an edge over the Sun JVM due to its faster garbage collection. I have also had some experience with the Symantec JVM and found it very similar to the Sun JVM. Symantec's Java compiler is the fastest and has since been included with Sun's JDK 1.1.6 release. If you are writing 100 percent pure Java code, I highly recommend that you use the JDK 1.1.x from Sun to develop and compile your code and then implement your application using the Microsoft JVM. This curious combination gives the best performance and stability at the present time. Our results validate the benchmarks conducted on various JVMs. See the Resources section below for links to the Volano benchmark results and articles on JVM performance.
You are probably wondering if we tried compiling code with the Microsoft Java compiler and then running the application on the Sun JVM. It's very logical to think that this should indeed be possible, as per the Java language specs. However, our application, written in 100 percent pure Java did not compile using the Microsoft compiler. We persisted, but found that we would have to make a lot of changes to our code in order to get it to compile; and even then we couldn't be sure it would execute satisfactorily on the Sun JVM. So we decided to abandon this exercise. Given these differences between the two JVMs, it is crucial to design your Java application appropriately and, most importantly, to set the expectations of your user community. This will go a long way toward ensuring the success of your Java application.
Rules of thumb
I will try to summarize my recommendations from the two articles. These can be used as generic rules of thumb when designing and developing your next Java application and should help you steer clear of problem areas in the JVMs.
If you have similar findings or would like to contribute to my list of findings, please feel free to send me a note at firstname.lastname@example.org.
About the author
Navneet Mathur is a senior associate and technical team lead at Cambridge Technology Partners. Reach Navneet at email@example.com.
If you have technical problems with this magazine, contact firstname.lastname@example.org