Not using garbage collection

Minimize heap thrashing in your Java programs

1 2 Page 2
Page 2 of 2

The StreamBuffer interface is pretty simple; the constructor takes two parameters, a size and a reference to an object that implements the Recyclable interface. The size determines how big the internal buffer is, and should be large enough to be efficient but not so large as to be often wasted. The recycler is used to actually recycle the object when it is no longer needed.

This class then has a couple of housekeeping methods -- space(), and avail(). Space returns the amount of space left in the buffer area, and avail() returns how much data is in the buffer. By summing the two values, the total size of the buffer can be computed.

These methods are followed by read() and write. As you would expect, read returns bytes from the buffer whereas write stores bytes into the buffer. There is one difference, however: write takes no parameters, as there is only one data buffer to write into; internally it maintains the amount of data stored into the buffer and returns a boolean true when the buffer is full. The read method, on the other hand, is used by all of the consumers of this data, and while one reader may have read ten bytes, another may have read one hundred, so it takes a parameter from the client hasBeenRead to indicate which bytes the client has already seen. In this way the client can increment hasBeenRead after each call and get bytes sequentially out of the buffer. This method is defined to return an integer but only returns bytes from the buffer. This is to allow the return value to be overloaded such that a return of -1 indicates no more bytes are available whereas a byte with the value 0xff would be returned as 255.

Finally, the StreamBuffer implements the reference counting and recycling interfaces. These are pretty straightforward but you can check out the code if you are curious.

Now the stage is nearly set for the completion of my efforts to eliminate garbage through recycling and limited production.

Hooking them together

With the StreamBuffer object we can now call its write method when the client of our input stream writes bytes. This will cue up bytes in this object until one of two events occur: Either the client calls the flush() method, or the buffer fills up and write() returns true. In either case the buffer is immediately sent out on the data channel.

In the new implementation of insert() in the DataItem class, the reference is dropped into the queue of every listener on the channel. Each time the reference is put into a queue, insert() calls addRefCount() on it. Thus, once "delivered," the object's reference count is now the same number as the number of clients who want to read it.

Each client is then notified of the arrival of this new object, and the object is removed from the queue. In the case of DataChannelInputStream I chose to actually requeue the object in the object. The downside was that I needed another class for maintaining this queue, but the upside is that the channel buffer, which was set to 1024 originally, could be set to a much lower level (I chose eight). This queue was built into a class named StreamBufferList.

The DataChannelOutputStream class did not change too much from my first attempt; in fact, the only two significant additions were a recycle method that would receive the StreamBuffers after they had been read by all of the readers, and a StreamBufferList object to keep free buffers around for reuse.

The DataChannelInputStream class changed somewhat more radically.

First the run() method was rewritten to deal with StreamBuffer objects instead of Integer objects. Note that non-stream buffer objects are simply ignored, which allows for bogus producers sending data to this channel. Further, since the StreamBuffers replace the buffer array, they are put into a queue using a StreamBufferList object.

Next the read() method was rewritten to read data out of the current StreamBuffer rather than the circular array. Further, when the read exhausts the StreamBuffer, the recycle() method is called on the object. If this was the last client to read the data, the object sends itself back to the DataChannelOutputStream that produced it to be recycled.

Finally, the avail() method in the StreamBufferList class was used in conjunction with the avail() method in the StreamBuffer class to implement the avail() function in InputStream. By doing this, our virtual terminal class can ask the stream how many bytes are available and read them all in one operation.

Wrapping up

So was I successful? In minimizing the impact on the heap by my virtual terminal I was quite successful. Whereas the simple version would generate tens of thousands of objects, the modified version generates only tens of objects. You can always check your impact on the system using the -verbosegc flag to the Java program. For example, to run the appletviewer using this flag, type the following command:

    java -verbosegc sun.applet.AppletViewer URL

The moral of this story is that you can improve performance of your Java applications by not creating excess objects to be garbage collected. This is true for two reasons. First, the smaller heap requirement will keep the amount of virtual memory that the run time uses smaller, which results in less swapping. Second, while it is true that the same number of cycles are used whether or not it is the garbage collector freeing memory, it is less expensive in terms of CPU cycles to reuse an existing object than it is to allocate a new copy of that object. A valuable tool in the Java programmer's toolbox is the knowledge of when not to call new.

Chuck McManis is currently the director of system software at FreeGate Corp. FreeGate is a venture-funded start-up that is exploring opportunities in the Internet marketplace. Before joining FreeGate, McManis was a member of the Java group. He joined the Java group just after the formation of FirstPerson Inc. and was a member of the portable OS group (the group responsible for the OS portion of Java). Later, when FirstPerson was dissolved, he stayed with the group through the development of the alpha and beta versions of the Java platform. He created the first "all Java" home page on the Internet when he did the programming for the Java version of the Sun home page in May 1995. He also developed a cryptographic library for Java and versions of the Java class loader that could screen classes based on digital signatures. Before joining FirstPerson, Chuck worked in the operating systems area of SunSoft developing networking applications, where he did the initial design of NIS+.

This story, "Not using garbage collection" was originally published by JavaWorld.


Copyright © 1996 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2