Letters to the Editor

Quite a few readers responded to last month's JDK 1.2 bug report with skepticism at best; author Kieron Murphy takes on the critics. Plus: VolanoMark's John Neffenger addresses a mailbag full of JVM scalability queries; Merlin Hughes tackles the mathematical intricacies of 3D programming in Java; Allen Holub defends his approach to coding; and technical Q&A with Chuck McManis, Todd Sundsted, Bill Venners, and John Zukowski

"First bug bites Java 1.2 -- Mozilla stung" by Kieron Murphy

Read

What bug?

Kieron,

I am curious about what bug Felten and co. have found that needs to fixed in "all JVMs." I have looked in great depth at the JVM definition, and do not believe any class loading or type verification bug exists in the JVM.

Vijay Saraswat

"Early access" software clarification

Kieron,

I think "early access" to software is intended to flush out bugs. Early access software should not be used in a production environment; it should be used only by developers.

Mike LaRocca

Sensationalism?

Kieron,

The sensational title of your article does not match its reality. How can you state in the title that the first JDK 1.2 security bug has been discovered when, at the end of the article, you state that it's not exploitable in the JDK?

Li Gong

Either/or

Kieron,

Which is it? Your article states in the second-to-last paragraph: "I've just taken a look at Communicator 4.50 PR1, and the holes still exist there." However, the link you provide for Princeton University Secure Internet Programming (SIP) report of July 1998, clearly states:

This flaw is fixed in Navigator 4.5. We have verified that our demonstration applet does not work on Navigator 4.5.

Please clarify.

Name withheld

Kieron Murphy responds

Readers, To begin, any defects in the composition of the article are mine alone. I stand by the article, but admit it could have been better. To clarify matters, I offer the following comments on some of the criticism the article has received. The main complaint seems to be that the conclusion does not satisfy the premise. One reader notes: "The sensational title of your article does not match its reality." I made several attempts to contact appropriate representatives at Netscape and Sun for comment on this point, but neither organization responded in any detail for the record. Informed sources told me, however, that the dynamic linking problem in JDK 1.x and all contemporary implementations of the Java VM is a serious flaw. A distinction must, of course, be made between the existence of a flaw and its actual exploitation. The importance of the Princeton hole, in my opinion, was due to its breaking the barrier of successful type confusion in current and planned designs -- especially, by mounting a three-phase attack. That type safety was only broken in Navigator 4.x should not be considered a disturbance; it should be an alarm. Princeton's SIP researchers have long been at work on a theory that the dynamic linking mechanism in Java may have unsound technical properties. The Hostile Applets home page's class loader bug, discovered earlier this year, and the security manager trick in Navigator simply provided the topography for an attack. In my view, they succeeded in accomplishing their mission. Java developers should be very defensive in creating code. Designing security policy for users is of paramount concern for the success of the language and platform. So, I do not think my premise and conclusion disagree. In a related vein, another reader writes that a discrepancy exists between the quote of Dr. Mark LaDue -- "I've just taken a look at Communicator 4.50 PR1 and the holes still exist there" -- and a statement found on the SIP site -- "This flaw is fixed in Navigator 4.5. We have verified that our demonstration applet does not work on Navigator 4.5." This person has me by the short hairs. Briefly, two answers. First, in editing the final copy, I probably quoted LaDue out of context. Though his hostile applets are essential to the overall Princeton attack, in going through my notes, I realize his remark applied only to his own applets at the time I wrote the article. So I am guilty of rushing. In this regard, I believe I owe JavaWorld readers an apology. Second, the SIP site currently states: "Last modified: Wed Jul 15 16:22:00 EDT 1998." In my notes, I have a printout of the SIP site taken on July 16. It does not include the statement in question. I do not know when this addition was made, but I did not use it in my article, because I was unaware of it. I did know that Netscape had been apprised of the hole about a week earlier. Another reader writes to remind me that "early access ... is intended to flush out bugs." I wish I could kiss this guy. Sometimes commentators of all stripes forget the big picture. Software development is just that: code in progress. News readers want to hear the latest dispatches from the frontlines, and news writers try to give them that, as rapidly and well as we can. We need to remember that we are all communicating with one another because we are trying to help one another. We are not on different sides. Kieron Murphy

"Which Java VM scales best?" by John Neffenger

Read

For future consideration: IBM OS/400

John,

I'm interested in seeing how the IBM OS/400 Version 4 Release 2 JVM compares to the other environments mentioned in your article -- especially against native-compiled Java. I bet I'm not alone in my curiosity. Maybe a future article?

Jack Callahan

IBM AS/400 VM -- "most scalable"?

John,

I'm surprised you didn't include the IBM AS/400 Java VM in your benchmarks. IBM is advertising the AS/400 VM as the "most scalable" and only VM that addresses the threading and cleanup issues that have plagued other platforms. Also, as far as I know the AS/400 VM is the only pure 64-bit Java VM commercially available.

With the introduction of the 0K 170 AS/400 models earlier this year, the AS/400 platform should receive consideration when evaluating a scalable Java platform.

John Lambert

Jack and John, I would like to see how all of the other IBM systems perform with respect to Java: IBM AIX, OS/390, and OS/400. It's just a little harder (and it would be costly) to set those systems up myself in our lab. I would have included AIX but I was told IBM no longer supports an Intel version of that OS. I restricted my tests to operating systems that are available on the Intel platform, since it's impossible to compare Java virtual machine implementations once you start varying the hardware underneath. I made an exception with the Mac OS, since we have quite a few customers running Internet servers and our VolanoChat product on the Mac. I included the scalability tests on Solaris SPARC just to show that other hardware architectures can affect processor scalability, but only after I made the fair comparison to Windows NT with Solaris Intel Edition. Hewlett-Packard HP-UX, OSF, SCO OpenServer and UnixWare, and Silicon Graphics IRIX were also left out this time around. With well over 100 Java licensees, it's getting impossible to test all of the latest Java virtual machines on all of the available hardware platforms and still publish timely results. Once SPEC publishes its server-side Java benchmark suite, we'll be able to get these results directly from the Java vendors themselves. John Neffenger

What about Symantec?

John,

Symantec claims its VM is faster than Microsoft's VM, but it isn't included in your comparison. Do you have any unpublished numbers for this VM?

Christof Baumgartner

Christof, I did include Symantec with the following Java virtual machines: JavaSoft JDK 1.1.6 JIT Update for JDK 1.1.6 Early Access 2 (Symantec JIT compiler Version x3.00.050) Novell JDK 1.1.5 Symantec Java! JIT compiler Version 3.00.040(x) for JDK 1.1.x JavaSoft JDK 1.2 Beta 3 Symantec Java! JIT compiler Version 3.00.023(x) for JDK 1.2 See the small print under each Java VM system configuration. John Neffenger

What do you mean by "restart"?

John,

Great article. When I see the word scalability I always groan because of the inevitable misuse, but you were on the mark.

It would be interesting to know the CPU utilization of the VMs in the test, i.e., are the CPUs at 100 percent?

In our stress testing, we've noticed some VMs (almost all the recent Sun Solaris VMs) repeatedly core dumping under heavy load -- that's right, a traditional Unix-style core dump, time after time. We do net management, and this is with a message-processing throughput test that is very CPU bound. In fact it's sort of different than your tests in that the message is being parsed from ASCII and translated into CORBA. It isn't a connection-heavy test.

I'm also under the impression that, if the app threads are busy enough, some VMs never get around to running garbage collection. Have you heard this?

When you talk about having to restart a VM during a test to get up to a certain number of connections it's not clear what restart means -- is this the same as rerunning the test?

I noticed that some of the tests ran with the -ss flag. Based on what may be voodoo, we've started running our apps with -oss too, because it seems that there may be some garbage-collection/thread-depth bugs under Solaris.

Also, does your server examine or otherwise modify messages as they come in, or does it just basically pass messages out to all other sockets and chat users?

Dave Spencer

Dave, I'm glad you liked the article. CPU usage was 100 percent in the local loopback tests, but often fell below that when driving the fastest Java virtual machines over the network. With the fastest Java VMs, the test driver or the network itself eventually becomes the bottleneck. You should report those core dumps to Sun. Ever since we moved to the JDK 1.1.5 Solaris Production Release, we've seen no such errors, but we saw plenty of them prior to that release. I've never seen the garbage collector simply fail to run due to heavy CPU usage (this would result in a java.lang.OutOfMemoryError). I tried to run the network connection scalability tests without restarting the server, meaning that I left the server running as I started up the client, first with 2 connections, then with 100, then with 200, and on up to 900 connections. In each of these tests, I started up the client with a new connection count. When I say I had to restart the server, it means that, for example, the test at 300 connections completed successfully, but then when I tried to start up the client with 400 concurrent connections, it failed. I then restarted the server, starting the client with 400 connections, and it worked. I haven't yet messed around with the -oss flag. Many of the Java vendors suggested I reduce the native stack size (-ss), but none of them mentioned the Java thread stack size. Finally, The VolanoMark server tries to do as little as possible with the message, but it does have to read-in the message (using a DataInputStream), examine some of the strings, and write-out the message (using a DataOutputStream). It uses a dynamic protocol based on object serialization, so it also uses the Class.forName and Class.newInstance methods to deserialize the objects. John Neffenger

JDK 1.2 Beta 4 results

John,

Just wondering if you got your VolanoMark numbers from JDK 1.2 Beta 4. Beta 4 is noticeably faster than Beta 3.

Also, have you found anyone to try HotSpot yet? Not that you could publish the results, but that would be an interesting test.

Stephen Drye

Stephen, I just tried JDK 1.2 Beta 4 and got the following result for the local loopback test:

Java virtual machine: JavaSoft JDK 1.2 Beta 4 Scores: 1275, 1284, 1288 Average (best 2 of 3): 1286

This result shows a 2-percent performance improvement over the JDK 1.2 Beta 3 release when running VolanoMark 2.0.0 Build 137. Other areas of the JDK not tested by VolanoMark may, however, have experienced much larger performance improvements. I haven't seen HotSpot yet, so I can't comment on it. John Neffenger

Client vs. server scalability

John,

In an otherwise excellent article I have two negative comments:

  1. You really ought to be testing with Warp Server SMP. OS/2 Warp 4 (client) is not an appropriate choice for heavy-duty server tasks requiring hundreds of connections, precisely what your benchmark is exploring. (Please let me know if you need a copy of Warp Server SMP.)

  2. Windows NT Workstation isn't appropriate for these tests either. If I understand the Microsoft licensing correctly, Workstation is not licensed for the number of connections you're throwing at it. You probably ought to be using Windows NT Server.

Again, thanks for exploring this issue.

Timothy Sipples

Timothy, Actually, I used the client versions of all the operating systems that have both server and client packaging: Solaris 2.6 Desktop Intel Platform Edition, Windows NT Workstation 4.0, and OS/2 Warp Client 4.0. NetWare is only a server, and Linux and FreeBSD don't make the distinction. (For the processor scalability, I used Windows NT Server and Solaris Server.) As far as I can tell from my own tests, there is no difference in performance, stability, or scalability in VolanoMark between Solaris Desktop and Solaris Server, nor between Windows NT Workstation and Windows NT Server. IBM tells me there is a difference in those characteristics between OS/2 Warp Client and OS/2 Warp Server, so OS/2 may be the only system I tested that has a less scalable client version. Although Windows NT Workstation 4.0 is not licensed for connections from more than 10 computers, there's no problem in running a program like VolanoMark from a single computer that simulates hundreds or even thousands of client connections. To quote Microsoft, "The issue isn't about 'connections,' it is about computers accessing local data and operating system resources and services." For more information, see http://www.microsoft.com/ntworkstation/info/ntlicensing.htm We recommend Windows NT Server 4.0 when running our VolanoChat product on the Internet. John Neffenger

Getting around file-descriptor limits

John,

It wasn't clear exactly which platforms you were achieving 2000, 2400 and even 4096 connections on. My understanding is that Solaris has a file-descriptor limit of 1024 connections and that to achieve higher numbers you would have to recompile the kernel. Is this how you achieved those figures or has Solaris 2.6 raised the maximum limit?

Any information on how to go about getting more than 1024 connections will be greatly appreciated.

Andreas Walsh

Andreas, I've seen up to 2000 (and heard of up to 2400) connections on Solaris 2.6. See the sections for FreeBSD, Linux, and Solaris at: Server Tips and Tricks: http://www.volano.net/guide/tips.html Here you'll find information on how I increased the file descriptors on each of these systems. Only Linux requires you to rebuild the kernel. In fact, you can change the settings for FreeBSD on the fly. Windows NT has no per-process file descriptor limit (or if it does, it's around 32,000), but it does have per-process thread limits (which I discuss in the article). John Neffenger

Client-side performance

John,

I have a question about your test application, VolanoMark. Does this client simulate up to 900 sessions in a single application? If so, were you not hitting CPU or I/O limits on the client platform? Given that the test application was also running on a JVM, it seems to me that you were measuring performance of the client and server -- not just the server.

Michael Lee

Michael, In almost all cases, the server-side (responder) Java VM was slower than the client-side (initiator) Java VM, resulting in 100-percent CPU usage for the server and much less than full CPU usage for the client. I wanted to run the tests with exactly the same client Java VM, so I chose the fastest and most stable JVM that could make it to 900 connections (Microsoft SDK for Java 3.0, for now). It would have been interesting to run the vendors against themselves -- the Linux OS and JDK on both sides, for example -- but then I wouldn't have been driving the server as fast as possible. It would also have been nice to "gang up" on the server machine by driving it with several client machines, but that takes a distributed coordination among the client applications, which doesn't yet exist in VolanoMark. Maybe VolanoMark 3.0. In any case, the SPEC group likes to keep hardware requirements to a minimum for its benchmarks -- one or two machines max. I ran the local loopback test precisely to avoid the effects of any bottlenecks such as the client machine or even the network connection itself. To make clear distinctions between the connection scalability of the fastest Java virtual machines, though, I think the choice is either to run the test driver on a much faster machine than the server or to coordinate multiple test driver applications from multiple machines. John Neffenger

Kaffe Open -- time for another look?

John,

I enjoyed reading your benchmarking of various JDKs. One JDK that appears to have been overlooked, due to its very recent release perhaps, is Kaffe Open (http://www.transvirtual.com/). It is a JIT compiler, from what I understand.

William Burrow

William, I tried off and on for a long time to get Kaffe to run our VolanoChat product, but I never managed to get very far. If I remember correctly, I tried it on Windows NT, FreeBSD, BSDI, Linux, and Solaris. I gave up about a year ago. It just wasn't at all stable, and I couldn't get more than a dozen or so connections. I'm sure all that has changed by now, so I'm eager to give it a try in the next round. John Neffenger

IBM JDK 1.1.7 on the horizon

John,

I just read your latest article in JavaWorld. Thanks for the nice words about IBM's JDK. I'm a little confused about the following:

IBM's recent updates to its operating system, TCP/IP stack, and JVM have given it a huge 50-percent performance improvement over the VolanoMark 2.0 tests I ran without the updates, but I could not get more than 400 concurrent connections with IBM's JVM.

However, later in the article, you write:

IBM JDK 1.1.6
  • IBM OS/2 Warp 4.00 FixPak 7 (Revision 9.031)
  • IBM TCP/IP Version 4.1 (SOCKETS.SYS: 5.3003, AFOS2.SYS: 5.3000, AFINET.SYS:> 5.3002)
  • IBM JDK Version "JDK 1.1.6 IBM build o116-19980605 (JIT: javax)"
  • Set THREADS=4096 in CONFIG.SYS.

This suggests that you did run with the updates.

We have made significant progress even since the 1.1.6 release. Expect the next crank of Java, 1.1.7, to be greatly improved on OS/2 both in throughput (more tuning and a better JIT) as well as more concurrent connections. Some of these changes may even make it into the 1.1.6 service stream.

Rajiv Arora Senior Software Engineer NCSD System Performance

Rajiv, I did run with the updates. My 50-percent improvement remark was comparing the score I got without the updates (780): http://www.volano.com/spec/index.html against the new score with the udpates (1216): http://www.javaworld.com/javaworld/jw-08-1998/jw-08-volanomark.html (Actually a 56-percent improvement, assuming I divided correctly.) I didn't publish the scores without the updates, but that might be unclear from the text. I'm glad to hear about the new improvements coming along! John Neffenger

Benchmark results: testing for pure speed

John,

I am wondering if the benchmarks are being skewed in any funny way as a result of having a single client machine. Have you run the tests with multiple client machines accessing the server? What are the results like? Were you ever CPU-bound on the server? On the client? I'd like to run the test with multiple client machines, each with a couple of hundred users.

Brad Hlista

Brad, As far as I can figure, the benchmark results on the network test are "skewed" in that the fastest Java virtual machines may have their scores dampened by the speed of the machine driving the test and the speed of the network itself. Such an effect is apparent when comparing the chart of the current Java VMs, where there is a wide variation in speed, with the results of the unreleased Java VMs, where all of them are fast. I have run with multiple clients, but to get a common score I need to enable the message counting on the server side, which affects the performance by about 5 percent and doesn't give a definitive score. To do it right, I need to modify the test driver (client side) to coordinate among multiple Java applications, perhaps on different machines. I'm afraid that, in an attempt to drive the system under test by several other machines on the network, I might just end up testing the speed of the network itself. That's why I test for pure speed using the loopback test with common hardware. Don't forget -- I'm not trying to set a speed record between two systems. In running the tests, I'm trying to find out if there are any huge differences in performance, scalability, and stability in the Java virtual machines. In fact, there are. But those differences are diminishing with the next batch of Java VMs arriving this fall. John Neffenger

Java Step by Step: 3D Graphic Java Series by Merlin Hughes

ReadRead

The need for speed

Merlin,

First let me say that I've enjoyed especially your last two 3D columns in JavaWorld; I'll be using information from them in the very near future (meaning I should have done it yesterday). Anyhow, I'm doing a project with Java and I have a problem. The problem is huge, but the solution -- if there is one -- shouldn't be. I have 3D MRI-data. The size is 256 x 256 x 256 pixels (or voxels). From that data I take 2D slices from different locations and different directions. The data is stored in either int[][][] or byte[][][]. (It's grayscale, but at some point I need to add color -- either on the fly or by converting data to RGB.) I have three simultaneous views to this data. Each view will ask for a slice, which the data will provide. The code is as follows:

 public Image getSliceY(int
                                       Y){
                                       int[] imageData;
                                       imageData = new int[sizeX * sizeZ];
                                       for (int k ....
                                       imageData[index++] = voxels[k][Y][i];
                                       return Toolkit.getDefaultToolkit().createImage(new
                                       MemoryImageSource(sizeX, sizeZ, imageData, 0, sizeX));
                                       }
                                      

No problems yet. This works. The problem is, this is slow, and it takes a lot of memory. It's bearable with 3D image data of size 128 x 128 x 84 (the slices will be 128 x 128 or 128 x 84) on a 96-MB Pentium 166 and WinNT with Microsoft JViews (the fastest VM I have). But with the full images ... I just can't do it.

The solution? Well, that's what I come to you for. I've searched the Net and I haven't found any other way. Everybody uses createImage, but that doesn't seem to work. The process is too slow. I'm a bit surprised, because I'd imagine in doing animation one would have to use something faster.

Hugo Gavert

Hugo, I don't have a complete answer to your question because the image stuff is all beneath the JVM and addressing memory problems and speed issues down there is hard. However, there is a new (as of JDK 1.1) MemoryImageSource method that may resolve some of your problems. Basically, create your imageData array once. Then create your MemoryImageSource attached to that. Then call memoryImageSource.setAnimated (true). Then create your image. Then, each time you update your view, fill in the initial image data array; don't change your MemoryImageSource or your image; just call memoryImageSource.newPixels (). This will recreate the Image and automatically refresh your display. If you examine the source to either of the articles, you'll see that I do just this. In your case, you'll obviously want to have three image data arrays, sources, and images, but the extension should be fairly easy. Hopefully this will sort out some of your problems. Another thing; when you're finished with an Image, call its flush() method; this frees up some resources. The ultimate answer is that using the JDK 1.1 Image framework isn't ideal for speed. It is possible that the JDK 1.2 Java2D or Java3D APIs may provide a solution; you may be able to more rapidly create a texture map and draw textured polygons or some-such. Have a look at those APIs. The problem here would be that your speed would be hardware-dependent; hardware with a limited CPU-texture bus wouldn't like your application. But it may be better than nothing. Merlin Hughes

How did you do that?

Merlin,

Great series on 3D graphics in Java! Your articles have really helped solidify my scattered knowledge of 3D graphics, shadowing, etc. Is the source code for the 3D planetary surface available? No matter how I tweak your example code, I get nothing that approaches your example image.

Mike Shipkowski

Mike, It's a bit convoluted to get from the example code to the code that produced the first image. (I created the image with a beta version of the code.) The following steps should get you there:

  1. Produce a background image. You'll have to hack the code to not perform perspective transformation and return a blue-black fade: Terrain:
     
                                           RGB blue = new RGB (0.0, 0.0, 1.0);
                                           public RGB getColor (double i, double j) {
                                           double a = getAltitude (i, j);
                                           return blue.scale (a);
                                           }
                                          
    Landscape:
      void eyeToScreen () {
                                           int w = getSize ().width, h = getSize ().height;
                                           for (int i = 0; i <= steps; ++ i) {
                                           for (int j = 0; j <= steps; ++ j) {
                                           Triple p = map[i][j];
                                           double x = p.x, y = p.y, z = p.z;
                                           scrMap[i][j] = new XY ((int) (x * w), (int) (z * h));
                                           }
                                           }
                                           }
                                          
    Rasterizer:
      public void savePPM (String file) throws IOException {
                                           FileOutputStream fo = new FileOutputStream (file);
                                           DataOutputStream dO = new DataOutputStream (fo);
                                           dO.write ("P6\n".getBytes ());
                                           dO.write ("# written by merlin\n".getBytes ());
                                           dO.write ((width + " " + height + "\n").getBytes ());
                                           dO.write ("255\n".getBytes ());
                                           for (int i = 0; i < width * height; ++ i) {
                                           int rgb = buffer[i];
                                           dO.write (rgb >> 16);
                                           dO.write (rgb >> 8);
                                           dO.write (rgb >> 0);
                                           }
                                           dO.close ();
                                           }
                                          
    The last fragment saves a PPM-format image. You may also want to disable shadows.
  2. Load the background image into the rasterizer. Rasterizer:
      int[] bg;
                                           public Rasteriser (int width, int height) {
                                           this.width = width;
                                           this.height = height;
                                           buffer = new int[width * height];
                                           bg = new int[width * height];
                                           zBuffer = new double[width * height];
                                           try {
                                           byte[] b = new byte[3];
                                           DataInputStream d = new DataInputStream (new FileInputStream ("bg.ppm"));
                                           d.readLine ();
                                           d.readLine ();
                                           d.readLine ();
                                           d.readLine ();
                                           for (int i = 0; i < width * height; ++ i) {
                                           d.readFully (b, 0, 3);
                                           bg[i] = (0xff << 24) | (((b[1] & 0xff) / 4) << 16) |
                                           (((b[1] & 0xff) / 2) << 8) | ((b[2] & 0xff) / 2);
                                           }
                                           d.close ();
                                           } catch (IOException ex) {
                                           System.err.println (ex);
                                           }
                                           clear ();
                                           }
                                           public void clear () {
                                           for (int i = 0; i < width * height; ++ i) {
                                           buffer[i] = bg[i];
                                           zBuffer[i] = Double.MAX_VALUE;
                                           }
                                           }
                                          
  3. Make a new fractal terrain to produce the landscape.
      double sea;
                                           public FractalTerrain (..., int seed) {
                                           ...
                                           rng = new Random (seed);
                                           terrain[0][0] = 
                                           terrain[0][divisions] = 
                                           terrain[divisions][divisions] = 
                                           terrain[divisions][0] = rnd ();
                                           ...
                                           sea = min + (max - min) * .5;
                                           }
                                           public double getAAltitude (double i, double j) {
                                           double alt = terrain[(int) (i * divisions)][(int) (j * divisions)];
                                           return (alt < sea) ? (alt - min) / (sea - min) * 0.1 : (alt - sea) / (max - sea) * 0.9 + 0.1;
                                           }
                                           public double getAltitude (double i, double j) {
                                           double alt = terrain[(int) (i * divisions)][(int) (j * divisions)];
                                           double b = (alt < sea) ? (alt - min) / (sea - min) * 0.1 : (alt - sea) / (max - sea) * 0.9 + 0.1;
                                           return (b < .5)  ? .25 + b * b : b;
                                           }
                                           // Ignore the names!!
                                           RGB blue = new RGB (0.0, 0.2, 0.0);
                                           RGB yellow = new RGB (0.4, 0.2, 0.0);
                                           RGB green = new RGB (0.6, 0.24, 0.0);
                                           RGB grey = new RGB (1.0, 0.4, 0.1);
                                           RGB white = new RGB (1.0, 1.0, 1.0);
                                           public RGB getColor (double i, double j) {
                                           double a = getAAltitude (i, j) * (0.8 + 0.2 * Math.sin (i * 7.0) * Math.cos (j * 7.0));
                                           if (a < .1) {
                                           return blue.add (yellow.subtract (blue).scale ((a - 0.0) / 0.1));
                                           } else if (a < .4) {
                                           return yellow.add (green.subtract (yellow).scale ((a - 0.1) / 0.3));
                                           } else if (a < .7) {
                                           return green.add (grey.subtract (green).scale ((a - 0.4) / 0.3));
                                           } else {
                                           return grey.add (white.subtract (grey).scale ((a - 0.7) / 0.3));
                                           }
                                           }
                                          
    The first difference you see here is that I specify a seed and construct the RNG with that seed. Then I just initialize the four corners to the same value. Keep in mind that this is old code, so it may need to be changed to fit in with the new code, but these changes should be obvious. The second difference is that I applied a wavy sea level and made the altitude-to-color transformation a bit less banded. I can't justify this particular approach; it's just what I had when I produced that image. The seed value I used for that image is 311. I used a roughness of .4 and vertical exaggeration of .7. This code should produce a reasonable image. (I also added a filter to remove spikes; a bit messy.)
  4. To add the planet, run the modified terrain, but produce a green-blue fade. Convert this to a JPEG and load it into the first article in the series. Finally, write an image compositor and mix them to create the background.
  5. Adding a second light is a lot of effort. I recommend that you, instead, soften the shadows. You can quickly do this by changing shade[?][?] = 0.5 (instead of 0.0).

Try that out and see what results you get. The image I created was at a grid resolution of 1<<8, but 1<<6 will produce a reasonably smooth result. If it's still not right, mail me your modified code and I'll give it a better look. Merlin Hughes

Image animation

Merlin,

Your 3D Graphics series is "Very worth reading!" Your explanation of difficult topics like algorithmic textures, supersampling, and spherical coordinate systems is both helpful and funny.

I'm hoping you can provide me with more explanation (and maybe some source code) about java.awt.image classes. It's easy to put an image on the screen with Java, but adding animation (like waving or rotating objects) is a little bit difficult. Can you help?

Bruno Salzano

Bruno, The

java.awt.image

framework just isn't fast enough for most animation purposes. The only way to animate images at a reasonable speed is to generate a bunch of

Image

objects, use a

MediaTracker

to get them ready for drawing, and then display them one after the other, like this:

int N = 16;
                                       Image image;
                                       Image[] images = new Image[N];
                                       In a subthread:
                                       // fill in images[0..N-1]
                                       // use a MediaTracker to ready the images
                                       int index = 0;
                                       while (true) {
                                       image = images[index];
                                       repaint ();
                                       Thread.sleep (100);
                                       index = (index + 1) % N;
                                       }
                                       public void update (Graphics g) { paint (g); }
                                       public void paint (Graphics g) {
                                       if (image != null) g.drawImage (image, 0, 0, this);
                                       }
                                      

I am planning to write a column on 3D animation later this year (or early next year); until then, this is about all I can suggest. Merlin Hughes

"Programming Java threads in the real world" by Allen Holub

Read

Coding conventions -- a personal matter?

Allen,

My name is Laurence Vanhelsuwé, and I'm a JavaWorld contributor like yourself.

I read your first article on multithreading issues with interest. Its conclusion was extremely surprising and enlightening!

I was also very pleased to read that you consider C++ as a bad dream (same here).

On this topic, I've got a "religious issue" that I'd like to bring up: your code examples use C++-style identifier naming. In case you didn't know, Java "comes with" its own naming and coding convention which differs significantly from its C++ equivalents. This convention is documented in the Java Language Specification.

In an ideal world, I'd love to see every Java book/article author stick to the JLS code/naming conventions. Such consistent adherence to a Java language-tailored convention really does help to relegate C++ to "bad dream" status, rather than something which manages to stay with us in all kinds of counter-productive ways (for example, via C++-style identifiers in Java programs).

Your opinions on this would be appreciated.

In the mean time, I look forward to reading more of your articles on multithreading in Java.

Laurence Vanhelsuwé

P.S. FYI, several existing Java tools complain when you throw Java classes at them that do not adhere to JLS identifier naming conventions.

Laurence, We're going to have to agree to disagree on this one. I'm a strong believer in maximum readability of code. I am inflexible in following a personal rule: all nontrivial variable names be real words in English (that you can look up in a dictionary). This rule often makes the code self documenting, and makes it vastly easier for a non-native English speaker to maintain. To enforce this rule, I always run my code through a spell checker, and my spell checker treats underscores as punctuation. If I were to adapt the Pascal-style naming conventions favored by Sun, then none of my variable names would spell check properly. (I should also say that using underscores is not a C++ vs. Java issue. Pascal used mixed upper/lower case only because underscore wasn't permitted in an identifier. I asked Wirth about this once, and he said that it was an inadvertent commission from the language. Many C++ programs use Pascal-style names, many don't.) The conventions that are important are upper-case first letters on classes, lower-case first letters on methods and fields, all lower case in packages, and all upper case for constants. I follow these rules religiously. Since Sun consistently uses Pascal-style naming conventions, it's also easy for me to distinguish my method names from Sun names in derived classes. If I see MixedUpperAndLower, I know it must be defined in a Sun base class, not one of mine. Allen Holub

Java In Depth: "The basics of Java class loaders" by Chuck McManis

Read

Customizing custom class loaders

Chuck,

I am trying to develop a couple of classes to load jar files dynamically. I think the best way to perform this task is to create a custom class loader.

Your article states:

CustomClassLoader
                                       ccl = new CustomClassLoader();
                                       Object o;
                                       Class c;
                                       c = ccl.loadClass("someNewClass");
                                       o = c.newInstance();
                                       ((SomeNewClass)o).someClassMethod();
                                      

However, you cannot cast o to someNewClass because only the custom class loader "knows" about the new class it has just loaded.

There are two reasons for this. First, the classes in the Java virtual machine are considered castable if they have at least one common class pointer. However, classes loaded by two different class loaders will have two different class pointers and no classes in common (except java.lang.Object usually). Second, the idea behind having a custom class loader is to load classes after the application is deployed so the application does not know a priori about the classes it will load. This dilemma is solved by giving both the application and the loaded class a class in common.

In my opinion, this restriction invalidates the solution of using a class loader to load jar files dynamically, because files included in a jar will be very different and cannot extend a common class or implement a common interface. I need to refer to specific methods of classes and not to generic methods of a common extended class.

How can I avoid this restriction? Do you have any solution? Perhaps I didn't understand the main idea about the cast in a custom class loader. Is there a site where I can find more docs on this theme?

Chemi

Chemi, A couple of things:

  1. For the first handoff, the initial class loaded must share an interface that was loaded by both the custom class loader and the primordial (internal) class loader.
  2. When a class is loaded by the custom class loader, any classes it loads are loaded by the custom class loader and don't have this restriction.

So, you write a class loader that loads classes dynamically from jar files. You then write an "app loader" that shares an interface with the internal class loader that you have defined. Consider the following interface:

public interface AppLoader {
                                       public void main(String args[]);
                                       }
                                       Now you write an application loader like so:
                                       public MyAppLoader implements AppLoader {
                                       /* implements the interface */
                                       public void main(String args[]) {
                                       Constructors cc[];
                                       if (args.length == 0)
                                       return;
                                       Class c = Class.classForName(args[0]);
                                       cc = c.getDeclaredConstructors();
                                       cc[0].invoke( ... );
                                       }
                                       }
                                      

Call your custom class loader on the applet loader, then cast the result to an instance of

AppLoader

, invoke its main method with the name of the class you want to run and the constructor to use, etc. That class will never know it was loaded by a custom class loader (until it tries to do something involving security -- but that's a different question.) The code described is called a

trampoline

and it creates a class loader context, then loads a helper class that loads the target class. Chuck McManis

How-To Java: "Using the Graphics class" by Todd Sundsted

Read

If it's arrow heads you want...

Todd,

I'm having problems drawing lines with arrow heads. Do you have any idea how should I go about doing it ?

Peggy

Peggy, Unfortunately, the Graphics class supports only the most basic drawing operations. If you want arrow heads, you'll have to draw them yourself. To solve problems like this in the past (and to avoid having to reinvent code with each new application I write) I've created graphics helper classes. These helper classes hold methods for drawing advanced graphical objects. I'd suggest creating a class with methods for drawing lines with arrow heads of various sizes and shapes. To my knowledge, no one has done arrow heads yet. It might make a nice package of code to publish on the Net. Todd Sundsted

I can't pass my applet!

Todd,

Do you know how to pass Image objects obtained in an applet via the getImage() method to "public" helper classes -- i.e., a class that draws a custom-made ImageButton -- without getting an IllegalAccessError in Netscape?

I can draw my image on my applet, I just can't pass it to my ImageButton class. Any thoughts?

Ellen Lay

Ellen, An llegalAccessError is thrown by the Java virtual machine when a method of a class tries to access a field or method of another class that it doesn't have access to (because it's private, perhaps). This is usually caught by the compiler. Sometimes it isn't. Here's an example: If a method in class A calls a public method in class B, then class A will compile. If you then change class B and make the method private, the access violation won't be caught until runtime -- whereupon the VM will throw an IllegalAccessError. I would suspect you're compiling with one compiler (javac from the JDK, perhaps) and running under Netscape. Each of these products uses its own class files. There is probably some incompatibility between the two -- and that's causing the error. I can't provide any suggestions as to what the problem is, but I'd begin tracking it down by running the applet in the appletviewer and in another browser. See what (if anything) changes. I suspect it will run fine in the appletviewer (because it uses the same class files as the compiler). Todd Sundsted

Under The Hood: "How the Java virtual machine handles method invocation and return " by Bill Venners

Read

Direct (interface) references

Bill,

I have a problem understanding one concept in your article. In the "The invokeinterface instruction" section, you state:

The invokeinterface opcode performs the same function as invokevirtual. The only difference is that invokeinterface is used when the reference is of an interface type.

Does this mean the reference of an interface is an instance of a class that implements a particular interface?

If this is correct, why can't the JVM have the same index for a method declared in an interface? In class references, the JVM knows about the index of a method in a method table. I'd like to know why this differs for interface references.

Shailesh Shah

Shailesh, Any class can implement an interface, and the methods that implement the interface can appear anywhere in the class declaration. This in turn means that the method pointers for the interface methods can appear anywhere in the method table for a class. Thus, with only a reference of the interface type, the JVM can't be sure where in the method table the method pointer for a particular method will show up. See my book Inside the Java Virtual Machine (Chapter 8 "The Linking Model" in the section called "Direct References") for a nice example to your question. Bill Venners

Design Techniques: "Design for thread safety" by Bill Venners

Read

Synchronized methods -- faster than you think

Your article is a good introduction for people new to multithreaded systems. A couple of comments:

  • Your comment that synchronized methods are four to six times slower than non-synchronized methods is misleading. In fact there is almost negligible overhead for methods that do any real work.

  • Your approach to multithreading is for novices, and it does a good job for them. As a frequent user of multithreaded design, I would like to see a design pattern approach to building large-scale designs that guarantee against deadlock.

    For example, I do bi-directional communication using Java objects, where each object has a thread of its own. In my initial designs, I could get into deadlock if A sent a message to B while B was sending a message to A. I developed a design pattern to avoid this problem, which I now apply to all bi-directional messaging systems.

Keith Musser

Keith, To answer your comments:

  • I try to be kind of pessimistic with these numbers, because I don't want to be part of the Java hype. The four to six times number was very commonly quoted about the JDK, but it may be that the more recent JDK versions have improved significantly in this department, as your experience shows. Sun also promises that in its Hotspot VM, synchronized methods will be "free." Although sometimes when writing in Java, you know what VM your application will be running on, in general, I think you should write to the so-called Java platform, which implies that your application should be designed to run well on lots of different JVM implementations. That includes old JDK implementations that don't have the latest VM improvements, and of course, VMs from vendors other than Sun. So I try to walk the middle road in my advice by saying, don't make things synchronized (or thread-safe) that don't need to be synchronized because of the possible performance trouble. But also, don't avoid synchronization where you need it. Hopefully I emphasized that last one enough.
  • I'm not quite done talking about threads. This article was just focused on thread-safety, or how to get threads to not inadvertently clash with each other. I also plan to talk about active objects (a pattern) and thread cooperation. I'd be interested in hearing more about your bi-directional messaging pattern. I want to organize my column (and my next book) around both guidelines and idioms, so if you've got a good idiom (or pattern) to propose, I can publish it at my Flexible Java Web site and book (attributed to you) and perhaps it will make it into one of my Design Techniques columns (also with credit to you as the identifier of the idiom and/or pattern). I like the sound of your pattern, from the little you've told me about it, because it sounds like it is very object-oriented--in other words, it sounds like you've got a way for active objects to communicate without deadlocking.

Bill Venners

"Microsoft sheds its Java skin -- again" by John Zukowski

Read

Choosing a development environment

John,

I enjoyed your recent article. My question is how to marry, or whether to marry, Visual J++ and JDK 1.1? I am new to Java and don't know which development environment to establish.

Is it one or the other...or should I invest time and energy with both? Have you written on this topic before?

Ed Vesely

Ed, If you want to visually create forms-based programs based on standard Java components (JavaBeans/JDBC), you could do better than VJ++. If, however, you want to create Windows-specific programs, but don't like VB/Delphi, then VJ++ may be your answer. While VJ++ allows you to work with 1.1 JDK, it is strictly compiler support, not visual development support. If that is what you need, then you can use VJ++ as the integrated debugging support (it's much better than the Java debugger). If you want a visual development environment for creating front ends for database applications, check out tools like JBuilder, VisualAge for Java, PowerJ, and Visual Café for Java. Regarding the last part of your question, if you can read Japanese, I have an article comparing JDK 1.1 development with VJ++ development in the August 1998 issue of JavaWorld Japan (hardcopy only). John Zukowski