HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Introduction
The advent of Java has traumatized the performance of an already slow Internet. Bringing up a new Web page now requires fetching graphics, applets, JavaScript, etc. You can`t change the world, but there are things you as a content-designer can do to make your Web pages load faster. This, in turn, translates into a higher likelihood that the user will continue browsing your site, rather than just giving up.

In this article I will discuss five techniques to improve Java loading performance:

  1. Use of the 'archive' tag in Netscape Navigator 3.0
  2. Implementation of a progress monitor
  3. Use of HTTP Keep Alives
  4. Dynamic loading of classes
  5. Class compression by symbol reduction

Background and Performance Measurements
One of the largest components of fetching a file via HTTP can be the TCP connection setup and httpd server accept() portion of the connection. Even on a high-speed server on a local area network, this can be a large speed constraint. On my local server, which is connected to my workstation via a 100MBPS LAN, this time is less than 1/10 of a second. Now, this doesn't sound like much. But when using Java, a mid-size Applet might require forty classes. That's almost 4 seconds of just setup time, not including any transfer time at all! If the server is loaded down at all, or is on a WAN or the public Internet, this time can be much larger, often in the 3 to 5 second range.

The average Java class file is usually less than 10K. On a LAN, that transfers in the blink of an eye. But to a user with a 28.8KBPS modem, that takes much longer. Even worse, the delay between your server and the end user can be quite variable due to parameters beyond your control: Internet backbone routers, congested WAN's, etc.

In Figure 1, I show the results I obtained from measuring the throughput and latency of my test web server (Apache 1.1.1). I stimulated an artificial number of connections per second (25), each from a different workstation. I then tried to get as many of the same URL as I could from another workstation. As the results indicate, there is an inflection point somewhere around 65 connections per second. The Apache server uses multiple server threads and a scoreboard. I assume this is around the maximum rate at which the sum of all my servers can still keep up, and after that the performance of the server threads starts to be the more limiting factor.

Figure 1
Figure 1

Archiving Classes
One of the new features which was added to Netscape Navigator in the 3.0 release was an archive' tag in the Applet definition in the HTML file. To use this, simply add 'archive=myclasses.zip' to your Applet instantiation in the HTML:

<applet archive="myclasses.zip"
code="EnterHere.class"
width=500 height=250>
</applet>

The Navigator browser will load this .zip file only once, saving you many connections to the server. If the end user is not using Navigator 3.0 or later (perhaps Navigator 2.0, or Internet Explorer), the 'archive' tag will just be ignored, and the classes fetched as normal. The same is also true for classes not in the zip file.

There are two things you need to do to create a .zip file of your classes:

  • Include the directory structure
  • Set compression to off' or 0' or Store Only'

For the version of zip' I use, this means invoking it with -r' and -0'.

Internet Explorer also has the ability to read Java classes from archive files. However Microsoft chose to use their Cabinet' file technology for Internet Explorer, making it incompatible with Navigator. The Microsoft technique is to add a new parameter (CABBASE) to the applet instantiation code. An example would be:

<applet codebase="http://www/%3cBR"> code=foo.class
<param name="cabbase" value="Foo.cab">
</applet>

The ability to read Java classes from .zip files has been around since the beginning of Java. There is also work underway by JavaSoft on something called Jars, which are Java-Archives. I expect that once Jars are finalized we will see Netscape Navigator and Microsoft Internet Explorer move to support them, providing a standard.

Progress Monitor
User interface studies show that users need feedback. If the user's browser just sits and does nothing while it loads yourapplet, they are likely to assume it is not working, and leave your site. This simple progress monitor shows a bar chart of the loading as it occurs. It adds only 2 Kbytes to the overall applet, and only one class. This means that it doesn't increase your download time by much, while at the same time providing some feedback to keep your users from leaving.

The progress monitor source code (see Listing 1) shows how easy it is to give feedback. Essentially, I've created a new applet, showProgress. It dynamically loads all the other classes, bumping a bar along as it does so. Because showProgress doesn't use any other classes from the server, it starts up almost immediately.

The basic design behind the progress monitor is very simple. A single applet downloads and starts displaying. It fetches the classes for the real applet, displaying progress information as it does so.

HTTP Keep-Alives
Also known as persistent connections, the HTTP Keep Alive increases performance vastly at no real cost to the client side. (There is a server side cost due to keeping a larger number of sockets active).

Netscape implements Keep-Alive with their Web servers, as does Apache, Microsoft Internet Information Server, and probably most other servers today. The HTTP Keep-Alive is defined in section 19.7.1 of the HTTP 1.1 specification (see http://www.w3.org/pub/WWW/Protocols/Specs.html).

A Keep-Alive, or persistent connections, works like this: The first connection to the site goes through the standard

s = socket(...)
connect(s,...)
write(s,...)
read(s,...)

but now, instead of closing this socket, the browser leaves it connected to the server for a time-out period. If a new connection to the server is required within the time-out period, the old socket (still connected) is reused. This saves all of the time of the TCP connection that I discussed earlier. Needless to say, this is a big win for Java users.

The httpd server sees that the client supports Keep-Alive connections by the "Connection:" header in the HTTP request. The HTTP request from Netscape 3.0, for instance, looks like this:

GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/3.01
Host: hppadbf
Accept: image/gif, */*

The httpd server responds with something like this:

HTTP/1.0 200 OK
Date: Sun, 01 Dec 1996 19:16:02 GMT
Server: Apache/1.1.1
Content-type: text/html
Content-length: 368
Last-modified: Sun, 10 Nov 1996 18:53:21 GMT
Connection: Keep-Alive
Keep-Alive: timeout=15, max=5

The Keep-Alive header on the reply is optional, and is used to pass parameters back to the client. In the Apache implementation, the time-out indicates how long (in seconds) the connection can remain open. The max parameter indicates how many times this connection may be reused (in order to prevent a single client from hogging the server).

Note: Keep-Alive support can only be used by your server for known length files. This means that CGI connections will require a separate TCP connect for each request.

Note: HTTP Keep-Alive does not work through a proxy server, so for users behind a firewall using a Proxy to get to the public Internet they provide no benefit.

I wrote a `C` program to test the transfer rates with different numbers of connection re-use numbers. The results are shown in Figure 2 for a local, high-speed network, and in Figure 3 for a wide-area, high-latency network. Predictably, re-using the TCP connection improves performance. However, not obvious is the fact that too many re-uses actually lower performance over a wide area network. My suspicion is that the server times out the connection after so many seconds, regardless of how active it is. This would then cause the client to discover this by getting a connection closed end of file, and have to open a new connection.

Figure 2
Figure 2
Figure 3
Figure 3

Dynamic Loading
Java is designed to be dynamically loaded. The loading of a class can be deferred until it is required, allowing Applets to start up faster at the cost of some runtime latency. However, the Netscape browser seems to pre-load all the classes it will need up front. As a programmer, you can avoid that by fetching your classes dynamically.

Dynamically loaded objects have some pitfalls, however. A new failure mode is introduced since objects might become unavailable between the time the Applet is started, and the time the object is needed. To the user, this might seem non-intuitive, expecting the Applet to either work right off, or to not work at all. Imagine if your word processor got all the code loaded to let you write your document, and then was unable to fetch the File menu to save it! Another problem is,of course, the latency introduced. To the user this will manifest itself as hiccups during the program each time a new object is required. These problems are not new or specific to Java; they have been around since dynamic paging and shared libraries were invented many years ago.

Class Compression
Most Java programmers are now aware of products like the "Mocha" de-compiler. It takes your .class file, and regenerates the source. Obviously if it can do this, there must be a lot of redundancy in the code. Fortunately, there is a companion product to Mocha, called Crema (http://web.inter.nl.net/users/H.F.van.Vliet/crema.html) to combat this. A pleasant side effect of Crema is that it makes your class files smaller without changing the functionality of them at all. Although your mileage will vary, I found that Crema made a representative sample of my classes an average of 12 percent smaller.

You needn't use Crema, you could just as easily apply this technique to reduce the size of your classes without attempting obfuscation. Simply replacing all identifiers with two-digit sequences should shave many bytes from your bytecode.

Conclusions
Archive your classes. Don`t make the user fetch a large number of files when instead they could fetch just one.

If you are concerned that people will leave your site for lack of feedback, or you have an applet that will take a large amount of time to load (more than 10 seconds), implement a download feedback monitor.

HTTP Keep-Alive connections improve the download performance of multiple document pages (embedded images, Java Applets, etc.). If your Web server is not already configured to allow Keep-Alives, enable them now.

If you can restructure your applet to dynamically load itself, you might consider this. I would, however, avoid using this technique and wait for the browser to do this for you since the functionality belongs in the operating environment, and not in the user code.

Compress (and obfuscate if you wish) your classes. There is no point in needlessly sending data over the Internet if the end user is just going to ignore it. The user of an applet really doesn`t care if a method is called supercallifragilistic' instead of foo.'

And above all, include a caution in your HTML if there is going to be a longer than normal or expected delay. Don`t cry wolf, cautioning everyone on every page, but if you have one page that takes 45 seconds to load where all your others take 2, let people know.

About the Author
Don Bowman is a software designer with a startup company in Ontario, Canada. He has worked with Java on HP-UX and Windows 95 environments. Don can be reached at [email protected]

	

Listing 1: Project Monitor Source Code.

import java.awt.*;
import java.applet.*;
public class showProgress extends Applet
{
 String classes[] =
 {
  "Main.DialogFrame",
  "Main.ScorePanel",
  "Main.HelpPanel",
  "Main.NetworkConnection",
  "Main.MediaHandler",
  "Main.UpdateThread",
  "Main.mainApplet"
 };
 Panel updateBar;
 private int loadedCount;
 private static final int offLeft = 10;
 private static final int offTop = 10;
 private static final int offBottom = 5;
 private static final int height = 30;
 public void init()
 {
  super.init();
  loadedCount = 0;
  show();
  updateBar = new Panel();
  updateBar.setFont(new Font("Helvetica",Font.PLAIN, 14));

  add(updateBar);
  updateBar.show();
  repaint();
  doLoad();
  updateBar.hide();

  Applet n = new Main.mainApplet();
  n.init();
  n.start();
 }
 public void paint (Graphics g1)
 {
  int barLen =
	updateBar.size().width * loadedCount / classes.length;
  updateBar.reshape(offLeft,
		    offTop,size().width - 2*offLeft,
		    height);
  Graphics g = updateBar.getGraphics();
  FontMetrics fm       = g.getFontMetrics(g.getFont());
  g.setColor (Color.gray);
  g.fillRect (0, 0,
	      updateBar.size().width,
	      updateBar.size().height);
  g.setColor (Color.blue);
  g.fill3DRect (0, 0,
		 barLen,
		 updateBar.size().height, true);
  int integerPercentage = (loadedCount * 100 / classes.length);
  String percentString = "" + integerPercentage + "%";
  g.setColor (Color.black);
  int PixelWidth  = fm.stringWidth(percentString);
  int PixelHeight = fm.getHeight();
  g.drawString(percentString,
	      (updateBar.size().width - PixelWidth)/2,
	      height - 2*offBottom);
  if (loadedCount > 0)
  {
   g.drawString("Loaded " + classes[loadedCount-1],
		 offLeft, height - offBottom);
  }
 }
 private void doLoad()
 {
  int i;
  for (i = 0; i < classes.length; ++ i)
  {
    try
    {
      Class c = Class.forName(classes[i]);
      loadedCount++;
      repaint();
    }
    catch (Exception e) { }
  }
 }
}

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.