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
 

I'd like to introduce you to a new JDJ series consisting of selected excerpts from my current book, Java Servlets: By Example. I've put a number of chapters into article format, hoping they'll give you some insight into the world of servlets and the sort of things we have to do at the server side. The first one really has nothing to do with servlets at all - it's more of a general overview of debugging and optimization techniques. This is by no means an exhaustive exploration, merely a toe-tipper.

Next month we'll begin our discovery into the world of servlets.

.   .   .

Java is a programming language. It is a programming language that runs programs known as class files. A class file is a sequence of instructions that perform some logical task. Sometimes these instructions run as expected...and sometimes they don't.

With most IDEs (integrated development environments) some form of debugger is available for use in developing applications or applets. However, due to the very nature of servlets, setting up a debugging environment isn't always easy.

This article presents a general debugging class that will aid you as the developer to quickly locate and fix problems. Just because Java makes coding easy doesn't mean that all standard coding practices go out the window. This article looks at a number of simple steps that can be done to speed up your class objects.

Debugging
Debugging programs is one of those annoying things that the majority of us have to perform at some point in our development life. As soon as we write a piece of code, be it a class or a method, we like to think it is perfect. "That won't need testing," we say to ourselves. However, nine out of 10 times it does require testing and sometimes quite intensive detective work. Many people dislike this process of debugging, fixing problems. It's even worse if you have to try and fix somebody else's code. Many developers view this as a necessary evil that simply has to be done. But they loathe it.

It's a state of mind. Think of it as a big game...the thrill of the chase. Somewhere in there, lurking under lines and lines of code, a small bug is causing the whole system to come crashing down. It becomes a game to try and flush him (or her - no reason why a bug can't be female!) out into the open. When thought of with this mind-set, that horribly small bug you've been avoiding suddenly becomes so much more attractive.

Many techniques for debugging are available to developers. Most rely on the tools provided by their development environment. In general, they allow the inspection of variables, stepping into code, freezing output and modification, to name but a few of the features available. Some rely on the old-fashioned method of debugging: print statements.

The print statement method is coming back into vogue. It has never had it so good. Thanks to Java, developers have the ability to print information that used to be privileged information of the debugger and compiler.

The most common types of bugs are the ones that are the most obvious when found. For example, a wrong variable name or incorrect assignment can leave us kicking ourselves for using it in the first place. The debugger generally tells us which line the program crashed in by highlighting it when it crashes. Very handy.

With the combination of Exception and Throwable, Java has given the developer the power to do this themselves. By honing in on the line that's causing the problem, these tools can build up a better picture of the bug as a whole.

Java uses the System.out.println(...) method to display messages on the console. Every object can be printed as a string, and this makes life so much easier.

However, the console isn't much use to servlets. Sure, at development time the developer can monitor the console, but more times than not the bug is highlighted through heavy use or after a period of time, when the console isn't being monitored.The servlet API defines a class method that allows the developer to send text to the main log file used by the Web server. This gives the developer a certain level of control, but still doesn't give that instant access to information that may be critical in the catching of a bug.

The class presented here gives all the functionality required to fully utilize the print statement method. This class has:

  • Easy access throughout the virtual machine
  • File logging
  • Socket logging
  • Exception logging
The main advantage of this class, as you'll discover, is that it's not restricted for use in the servlet environment. Any Java environment can use the features of the following debugging class.

Debugging Class
One of the most important things about any debugging class is simplicity. It can't impact the overall performance of the program. A class that takes up too much processing time can't be used as a debugging tool. This is the developer's equivalent of the age-old problem facing engineers who build measurement tools: how to build a tool that will accurately measure a given flow, substance, mixture, temperature - whatever - without interfering with the original environment in which the measurement will be taken. A system that measures the rate of water flow will unintentionally slow the flow down slightly by its very presence. A rod that measures temperature will either heat or cool the particles or atoms around about it. The smaller the impact, the better the tool.

Debugging is no different. If we wish to find a bug during development, this isn't considered a major problem. The tool is allowed to impact the environment if necessary. However, if it's to ship with a production version, to catch any of those long-term bugs it has to have minimum impact.

The class presented here serves both those criteria.

Class Structure
Let's assume that the class will be used throughout the virtual machine. Let us further assume that we don't want to keep a specific object instant to the class - we simply want to use it. We can achieve this by making the methods static, which means we don't ever have to instantiate the class manually.

Since we know that objects provide a toString(...) method, which returns a string representation of the object, we'll only support the ability to print strings. The calling method can then control exactly what it wishes to print. We can outline the class as shown below:

public final class Debug extends Object {
private Debug(){}

public static void println( String _line ){
//-- do some processing
}
}

Notice the constructor. We've declared it private to safeguard against anyone creating an instance of it. This ensures that only one instance of the class will exist in the virtual machine. Couple that with the fact that we've made our class final means that no one can extend it, that is, use it as a base class for another.

Simple Console Output
One of the most primitive things we want to control is whether or not the output is sent to the console. We can control this through a simple Boolean value, bSystemOut, that when set true will display all output on the console. We can write our core output function with this ability, as shown in Listing 1.

Notice the provision of an extra variable, bOn. This gives developers control of all debugging processes through the use of one variable. They can turn off all debugging with a single method call.

Irrespective of the output method, the same string will be displayed. This will be the original string from the developer with the current date inserted at the beginning. Having the date included in the output is very handy when it comes to debugging threads, for example.

Listing 2 shows the methods available to the developer for controlling the output of the debugging class.

File Output
Having output sent to the console is useful, but not always practical. For example, it would require watching the console for any messages, as they're lost once scrolled by. This of course assumes you don't redirect the console to a file, but this is generally operating system-specific.

Sending the output to a file is a much more convenient way for the developer to trace what has been printed out. We can build this functionality in to our debugging class very easily.

One of the things we have to ask of the file feature is not to overwrite any existing debug information. This is a fairly important feature, as subsequent runs of the servlet or application may wipe out valuable debug information.

Many of the standard file-handling classes that come as part of the Java libraries don't handle writing to the end of a file. The RandomFileAccess class, however, allows us to open a file and start appending data to it.

To make the class more efficient, we'll open the file once and leave it open for subsequent writes. This requires us to hold a reference to it throughout the life cycle of the debugging class. By initializing this variable with the value of null, we can easily determine whether the file is already available. If it isn't, we'll open it and move the file pointer to the end of the file so all output will be appended. The following listing illustrates this.

if ( bFile ){
try{
if ( OutFile == null ){
OutFile = new RandomAccessFile( filename, "rw" );
OutFile.seek( OutFile.length() );
OutFile.writeBytes( "\r\n------Logging Restarted------- n-ary
limited v1.3 ---------\r\n" );
}
OutFile.writeBytes( D );
}catch(Exception E){}
}
}

Notice again how we control the use of the file, through a variable called bFile. This particular piece of code will be placed inside the core print routine after the call to the system out.

This class assumes it will open the same file each time. Therefore the name of the file can be fixed in the class, and for this case all file output will go to a file "debug.txt". This file will be created in the current directory of the running application.

Socket Output
So far we have the ability to send output to the console and to a file. Wouldn't it be nice if we could log on to a port from anywhere and view the output as and when it happens?

Java has made the answer to this sort of question very trivial. Adding such networking capabilities to a class is no big task. We want to give the developer the ability to connect to a known port and then to view all the output through a standard TELNET session.

We need two things to handle this: (1) a thread that will listen for incoming client connections, and (2) a class that will handle the communications for each client.

Let's define the method for setting up a listening socket. We want to handle many client connections at once, so we'll create a simple loop that will listen for a connection and, once connected, create a class to handle that connection and then return to listen for more connections.

Since we already have a class - our debugging one - there's no point in creating another. So we'll extend this class to use the Thread class, and define a run() method that will be used to listen for client connections. Listing 3 shows this.

For us to send out all messages to all our clients we need to keep a reference to each client. This is achieved through the Vector class, which keeps a reference to the clientDebug class. This is the class we're going to use to handle each client, as shown in Listing 4.

The first thing this class does when it's created is to attempt to create an instance of DataOutputStream, which gives us a means to easily send strings to the socket. Since this is purely an output class, there's no need to get an input stream to the socket. If this fails, an exception will be thrown and the class reference will be set to null.

If it's successful, then some information regarding the current operating system and available memory is printed. This serves as both an informative read and also to confirm to the client that a good connection has indeed been made.

The debugging class will print to socket by calling the println(...) method. If the client is no longer available or has disconnected, an exception will be thrown. This will cause the method to return a false result, and the debugging class knows to remove the client from the list of available clients.

The method shown in the following code details the output to the client connections. It sets up an Enumeration to the Vector, which will allow it to run through the list easily. If the method returns false, then it's removed from the list.

private static void printToClients( String D ){
Enumeration E = clients.elements();
clientDebug CD;
while (E.hasMoreElements()){
CD = (clientDebug)E.nextElement();
if ( CD.println( D ) == false )
clients.removeElement( CD );
}
}

The following code controls the socket connections. At the core print routine, if the Vector that holds the client's connection is null, it's assumed the server hasn't been started. The server is then started by creating an instance of the debugging class and calling the start() method. This will invoke the thread and call the run() method.

if ( bSocket ){
if ( clients == null ){
new Debug().start();
clients = new Vector();
}

if ( clients.size() > 0 )
printToClients( D );
}

After it's been created, it calls the method shown in the previous snippet.

Exception Handling
In general, the time you need a debugging class is when things start going wrong. This usually manifests itself in lots of exceptions being thrown. Having the ability to handle these exceptions properly would increase the usefulness of a debugging class.

One of the really nice features of Java is the ability to display a complete stack trace. This is where each called method leading up to the problem is displayed, complete with the line number, if available. This information alone can save many hours of debugging time.

When an exception is thrown, the stack trace is available. This is through a method call from the printStackTrace() method. The output from this method is sent to the standard error stream. Therefore we must redirect it in order to get a copy of this invaluable data.

The next listing illustrates this redirection. Instead of sending the output straight to an output stream, we'll store it in a string and send it on through our normal print routine. This way the developer will still control the flow of output.

public static void printStackTrace( Exception E ){
ByteArrayOutputStream OS = new ByteArrayOutputStream();
PrintStream ps = new PrintStream( OS );
System.setErr( ps );
E.printStackTrace();
System.setErr( System.err );
println( OS.toString() );
}

Sending a stream to a string is a trivial matter. This involves creating a new instance of a ByteArrayOutputStream, which will hold all the data. Once collected, we simply call the toString() method and get the contents of this buffer returned as a string.

Using the Class
Now that we have the class built, let's look at how we can use it. The following code will most definitely throw an exception since the variable Temp is null.

try{
String Temp = null;
Temp = Temp.toLowerCase();
catch(Exception E){
Debug.println( "This will throw an exception" );
Debug.println( E );
Debug.printStackTrace( E );
}

When this occurs, a number of methods of the Debug class are called. In the default state this would mirror all the print statements to the console, file and any existing client connections.

Complete Source
Listing 5, the complete code for the debugging class, can be found on JDJ's web site, www.JavaDevelopersJournal.com.

Optimizing for Size
We can split this section into two further subsections - common sense, and not so obvious. First of all, a commonsense suggestion for reducing the size of a class includes naming variables and methods with smaller names.

Java has given us the ability to use really long method names, and while this is handy, they have to be stored in the class file. Reducing this can significantly decrease the size of the class file.

The Java library is a rich tapestry of classes that perform many tasks, and with over 1,500 classes to chose from, there's a lot of scope. So don't rewrite any functionality that may already exist.

Seems an obvious one, but you'd be surprised at the number of developers who have redeveloped standard classes that were unknown to them at the time of their rewriting. This reduces your code complexity, but in addition, the chances are good that the class you're using may be a native class and therefore run much faster.

Reuse methods. If you've developed a lot of different methods that don't really need an object instance to operate, place them in a class of their own and declare them as static methods. This will reduce the need to repeat them in classes that need them.

Now for some hints on the not-so-obvious tricks. One of the most common things you'll see in code is where strings are added together using the "+" operator. This is convenient, but also very slow. The compiler will generally replace each one with an instance of StringBuffer(...). For each "+" operator a new instance of StringBuffer will be created. Replace the operators with one instance and use the append(...) method for adding strings together.

Storing dates can be a pain, especially if you're moving between databases. Instead of storing the date, store the millisecond equivalent in a long. Not only does this save space in the database table and the virtual machine, but it makes querying on the dates very efficient as you're simply comparing two numbers as opposed to two dates.

Finally, remember to compile with optimizations turned on. This will automatically try to reduce your code size by eliminating dead code and converting some methods into inline calls.

Optimizing for Speed
Optimizing for speed follows the same principle. But always remember to do it - before and after timings. This way you can confidently convince yourself that your work has actually made an improvement and not by some fluke increased the execution time...which has been known to happen.

One of the most expensive operations you can do is exception handling. Try to replace exception blocks with logical tests, if possible, and reduce the number of lines contained within a block.

The synchronized method used for ensuring thread-safe execution is also a large overhead. Minimize the use of this where possible.

Creating objects in Java is a breeze. But they cost. Try to reuse objects as opposed to creating new ones. For every new one you create, the memory has to be allocated the garbage collector can come along and clean up all unused objects. This is common in loops.

If you're doing a lot of dividing, think of reworking your logic so you can divide by 2. Dividing is a very expensive operation, and if you can divide by 2, this can be replaced with shift operations. Many game developers optimize their code to take this into account and ignore the remainder that may be lost.

Warning
Before you modify all your code, remember that it's important to get it working first. Optimizing as you develop is never a good idea as this more often than not distorts the logic, which makes it harder to find unwanted features, or bugs. Many of the optimizations above will make a difference, but use them wisely. There's no need to completely redo every class you wrote just to save a couple of bytes. Use them wisely.

Author Bio
Alan Williamson is CEO of n-ary (consulting) Ltd., the first pure Java company in the United Kingdom. The firm, which specializes solely in Java at the server side, has offices in Scotland, England and Australia. Alan is the author of two Java servlet books, and contributed to the Servlet API. He has a Web site at www.n-ary.com. [email protected]

	

Listing 1: Modification of the Core Output Routine
    

static boolean bOn = true;
static boolean bSystemOut = true;
static SimpleDateFormat DateFormat = null;

public synchronized static void println( String Line ){
 if ( !bOn )
  return;
 
    if ( DateFormat == null )
    DateFormat = new SimpleDateFormat( "dd/MMM HH:mm.ss: ");

  String D = DateFormat.format(new java.util.Date()) + Line + "\r\n";

  if ( bSystemOut )
    System.out.println( D );
}


Listing 2: Controlling the Output


public static void On(){
  bOn=true;
}

public static void Off(){
  bOn=false;
}

public static void SystemOn(){
  bSystemOut=true;
}

public static void SystemOff(){
  bSystemOut=false;
}


Listing 3: Listening for client Connections

public final class Debug extends Thread {
  static Vector clients = null;
  static int SOCKET_PORT = 2000;

  public void run(){
    Socket sIN;
    ServerSocket sSERVER;

    try{
      sSERVER = new ServerSocket( SOCKET_PORT );
    }catch(Exception E){
      return;
    }

    for(;;){
      try{
        sIN = sSERVER.accept();
        clients.addElement( new clientDebug( sIN ) );

    }catch(Exception E){}
  }
}
}


Listing 4: Handling the Client Connection


class clientDebug {
  private Socket sIn;
  private DataOutputStream out;

  public clientDebug(Socket _sIn){
    sIn = _sIn;
    try{
      out = new DataOutputStream( sIn.getOutputStream() );
      out.writeBytes( "------Logging Restarted------- n-ary limited v1.3 ---------\r\n" );

      out.writeBytes( "[os.name]      = [" + System.getProperty("os.name") + "]\r\n" );
      out.writeBytes( "[os.arch]      = [" + System.getProperty("os.arch") + "]\r\n" );
      out.writeBytes( "[os.version]   = [" + System.getProperty("os.version") + "]\r\n" );


      out.writeBytes( "[java.version] = [" + System.getProperty("java.version") + "]\r\n" );
      out.writeBytes( "[java.vendor]  = [" + System.getProperty("java.vendor") + "]\r\n" );

      Runtime RT = Runtime.getRuntime();
      out.writeBytes( "[total memory] = [" + RT.totalMemory() + " bytes]\r\n" );
      out.writeBytes( "[free memory]  = [" + RT.freeMemory() + " bytes]\r\n" );
      out.writeBytes( "----------------\r\n" );

    }catch(Exception E){
      out = null;
    }
  }

  public boolean println( String _D ){

    try{
      out.writeBytes( _D );
      return true;
    }catch(Exception E){
      return false;
    }
  }
}





 

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.