Monday, April 30, 2007

Exception in "CompilerThread0" java.lang.OutOfMemoryError

Ever since we switched out build process from Java 1.4 to Java 5 (1.5.0_09) we have seen FindBugs crash with an OutOfMemoryError when started from ant. The whole thing is running under RedHat Enterprise Linux 4. The output is always the same:

[findbugs] Running FindBugs...
[findbugs] Exception in thread "CompilerThread0" java.lang.OutOfMemoryError: requested 134217736 bytes for Chunk::new. Out of swap space?

Our first attempts to increase the heap size with the -Xmx VM parameter did not help. We suspected the newer FindBugs release we had installed roughly at the same time, but this turned out to be wrong, because newer and older versions showed the same behaviour.

Armed with the fresh knowledge about the SAP Memory Analyzer just learned at jax.07 I tried to get a heap dump to try and find out what was causing the problem. Unfortunately the VM ignored my -XX:+HeapDumpOnOutOfMemoryError option completely (on Sun's VM Options page there it says -XX:-Heap... (with a minus), tried that, too).

Getting suspicious I realized that usually Java 5 VMs say something like OutOfMemoryError: Java Heap Space which was missing in this case.

I had never before seen that strange "CompilerThread0" in an error message, same for the "requested xx bytes for Chunk::new. Out of swap space?" part. Looking around I stumbled across this bug report (and some more, all related to this one - just follow the related bugs list): Bug #6487381 "Additional path for 5.0 jvm crash on exhaustion of CodeBuffer".

Apparently something goes wrong when the HotSpot JIT tries to compile something to native code for faster execution. One of the other bugs you can find by following the link above mentions that there is problem with situations where there is not enough room for further compiles in what is called the CodeBuffer: instead of failing gently and just continuing to run the application without compiling or throwing some older compiled code away the VM would just crash. This should have been fixed with Java 5.0, but apparently there is another code path in the VM that will cause a hard failure. (I wonder what may be requesting another 128MB(!), but anyways...)

From what the bug says this issue will be fixed with 5.0u12, however 5.0u11 is the most recent release to download, so no help there.

Attempting to find whether it might be a single method that was causing the crash reproducibly I tried the -XX:+PrintCompilation option. It produced lots and lots of output about compiled methods. A "guide" to its output can be found here: decyphering -XX:+PrintCompilation output.

As one might have expected, this did not reveal anything useful either. So the last resort was to switch to the Client VM which managed to complete the FindBugs run. In retrospect this explains why we were able to build without any problems on a Windows machine - the Client VM is default there unless you explicitly request the server.

For now we will leave it at that and try again as soon as 5.0u12 comes out.

Thursday, April 26, 2007

jax.07 Day 3

The (my) last day of jax started with Oliver Stauss talk "Umstellung eines Projekts von Ant zu Maven 2", dealing with switching a project from ant to Maven 2. For my taste there was too much history of Maven and too little concrete examples. I do get the impression that many speakers tend to go slow at the beginning of their sessions, elaborating too much on why and when the world was created, how mankind came about and suddenly realize that they have to hurry up to finish in time. Sorry Oliver, this is nothing personal, but a general observation I have made. But I am sure the session (which was good nonetheless) could have been a little more juicy had the focus been put differently.

To my delight the first keynote of the day, "The Role of Java EE in Enterprise SOA development at SAP" was not the self-adulation and buzzword-bristling thing I almost expected it to be. Instead it was quite interesting to hear about Java delopment in such a big company. Everything seems to be on a different scale... The keynote also contained a quick demo of the heap analyzer tool I wrote about before. In the meantime I have looked around and also got a comment on the blog, and I am quite sure the tool is rather unique as soon as you have somewhat larger heap dumps. I will definitely give it a try.

The second keynote "Java IDE(s) - Then, Now, and Eventually" by David Intersimone of CodeGear gave a nice overview of how Java IDEs developed and what may yet be to come. I have to admit that I still found it a little too "JBuilderish", but not too bad. Probably you can't help yourself after being with Borland for over 20 years ;-)

Before lunch I visited Karsten Lentzsch's "Gebote und Verbote für Swing-Oberflächen" ("Do's and don'ts for Swing UIs"). It was a very interesting and didactically well done talk about why it is very easy to build ugly UI's using Swing and what to do about it. I have to admit that I read much of what he wrote on JGoodies.com before, but it was great to have the opportunity to see updated examples and hear about these things first hand. I still have to convince my company to hire him for some days to get our UI a little more polish.

I really liked the conference, I believe it may even have been better than last year's. If I get the chance, I will definitely return in 2008.

jax.07 Day 2 (cont'd)

Adam Bien's talk about almost forgotten technologies in the Java world turned out to be rather boring, even though he introduced it as a "fun talk". He shortly mentioned FreeTTS and Sphinx and then talked a lot about JINI and some surrounding technologies. After only about 30 minutes the talk ended very early. Even though this gave me time for some fresh air, from retrospect I would better have spent the time in a different session.

I also skipped the keynote about Microsoft's Security Development Cycle. Somehow I was not too eager to learn about a security strategy of someone who has not proven to be a leader in that field.

Later that day I attended ""3D-Rendering and Physics Engines with Java SE 6" by Michael Bien (Adam Bien's younger brother). While the demos were impressive, Michael still has to practice a little more on how to speak freely and fluently to catch up with his brother :)

The second 30 minute short talk was Anrew Overholt's (Red Hat) "State of Eclipse and Linux distributions". As a Linux desktop user it is good to know that there is a lot of work being done to integrate Eclipse (itself and its extensions) into Linux distributions (not only Red Hat/Fedora for that matter). Andrew showed some interesting details and Linux tools they developed on their way, for example a ChangeLog plugin or a .spec file editor. The goal of their work is to enable any plugin developer (and Eclipse itself) to easily generate .rpms, .debs or whatever else is necessary to deploy their work on any given Linux distribution. While short it was still a very useful talk with a nice discussion at the end.

What followed in the evening was the best part of the day: Brian Goetz's keynote Concurrency - Past and Present and his session on "Java Performance Myths", containing valuable information about the inner workings of the VM and the HotSpot just-in-time compiler, all packaged into a fun talk/session. Ending at 21.45 it marked a good close-up for the day.

Wednesday, April 25, 2007

jax.07 Day 2

This morning my initial session was "Design Patterns for Security", held by Bruce Sams. I do not know whether it was too early for me or the topic was really that boring. While the ideas presented were not wrong or unusable they however where not really new. So I really had to concentrate too keep listening, waiting all the time for the moment the talk would finally take off for me. It didn't. And in retrospect I believe much of what I heard was already said at last years session, just under a different title.

The second talk was Dierk König on Groovy. At the very beginning we moved from the originally planned room to the main hall, because there were too many people there to pack into the small conference room. This cost about 5 minutes, however Dierk nicely compensated. For me personally I did not learn anything new, because the talk was obviously intended to inform people about the language that had not heard about it before. Nevertheless the talk was good and the examples impressive (a basic Swing-based Flickr image viewer in about 30 lines of code and perhaps 5 minutes of time). I am not quite sure if I will attend the Grails session tomorrow, but I will definitely go to the "Einsatz-Patterns für Skriptsprachen in Java" ("Usage-Patterns for scripting languages in Java") talk.

For this afternoon I am not quite sure where to go. Either it will be Java Runtime Performance Monitoring or "Beinahe vergessene, aber nützliche Java-Technologien" ("Almost forgotten, but useful Java technologies") by Adam Bien just because it is probably going to be fun :). Even if I go to the latter one, I will have a look at JInspired's JXInsight, because it was pointed out as a less expensive alternative to PerformaSure.

jax.07 Day 1 (cont'd)

Alright, yesterday's memory analysis talk was not that bad in the end. In fact their tool has its advantages over the usual suspects (at least I have never tried to load a 6GB 64bit heap dump with one of the tools I know). Apparently it only needs a somewhat powerful machine to initially parse the dump and generate some indices, but after that, any decent 32bit machine will work for the analysis. I will probably give it a try when I am back at work - "fortunately" we have been seeing some OutOfMemoryErrors while using FindBugs from the ant build. Maybe this can give us an idea.

Todays final session - after the "Web 2.0 and SOA" keynote (anyone else tired of hearing this everywhere?) was called "Schweinkram: Abstrakte Klassen dynamisch generieren" ("Nasty stuff: Dynamically generating abstract classes") by Michael Wiedeking. This one was really great. Not only was the topic very interesting, but the speaker was also presenting it in a very humorous but still professional way. Top ratings for this one, although I am not really sure I would want the things he talked about in any production system. Nevertheless it was very cool information.

Without going into too much detail I just try to summarize what the talk was all about: Trying to set up some tests for his code Michael realized that Java's proxy objects could not be used, because they can only be created for interfaces, not for abstract classes which would have been required. So he started figuring out how to do it anyway and came up with a (still experimental) way of changing a class's bytecode on the fly by either adding new methods or even replace on method's code with that of another. Very intriguing any very informative.

Tuesday, April 24, 2007

jax.07 Day 1

Today jax.07 started (at least for me). After a somewhat boring opening speech and an (almost equally boring) keynote by Sun's Tim Boudreau titled "Using the Right Tool for the Job" I went to "Schneller fahren auf mehr Spuren? Multithreading im Zeichen von Mehrkernprozessoren" ("Driving faster on more lanes? Multithreading in the light of multi-core processors"). It was quite interesting although the title was a bit misleading. I would have called it something like "Introduction for JDK5/6 concurrency".

The next session was "Performance Anti-Patterns" by Mirko Novakovic which was quite worthwile. While not presenting much new information for me it at least reassured me that you are never alone with your problems and that other people are not necessarily better off :) Once again I saw a demo of PerformaSure which seems to be a useful product. If only it were not so expensive.

Lunch was surprisingly good, remembering last years bad experience...

Right now I am listening to SAP AG's Vedran Lerenc and Erwin Margewitsch on "Java EE Application Server und der Java Heap - effektive Speicheranalyse" ("JEE App Server and the Java heap - effective memory analysis). From what I have seen so far, there is nothing I have not seen with JProfiler before. Nevertheless it is quite interesting.

Sunday, April 22, 2007

jax.07

Tomorrow I will be leaving for Wiesbaden to attend jax.07. I am looking forward to see a lot of good talks and some people in person who you read so much about (or from them for that matter). I think it is one thing to have a few books on the shelf with names like Brian Goetz on them or having seen them on some videostream. But I believe it's a different thing to actually see them in person.

After my experience with the food last year I will probably go outside for lunch. The venue is very close to a pedestrian precinct, so there should be ample opportunity to keep away from gastrointestinal disorder this time :)

Saturday, April 21, 2007

Upgrade to Feisty Fawn

On Thursday I downloaded the Feisty Fawn alternate CD from Ubuntu.com for a distribution upgrade. Because I did not have the nerve to first burn a CD I just loop-mounted it to /cdrom and started the upgrade.

Just like last time it went off to download another 350MB from the net. Once that was done it started installing the new packages. On the way it asked questions on some packages - I would have liked this to happen first, so that I could have let it run unattended. In all it took about 2 hours before it asked me to reboot.

I did so, just to look at the nice bootsplash and then be confronted with a message about a non-functional X11. On the command line I quickly realized that it was the same problem that I have had before several times: it had not installed the necessary linux-restricted-modules-generic package for the new 2.6.20.15 kernel, needed for my GeForce card.

Using aptitude I installed it and rebooted. This time I could log in to X. I had hoped that such problems were a thing of the past...

Another thing that still does not work is Azureus. Exactly the same problem as before (see the previous post), exactly the same fix: Just replace /usr/share/java/Azureus2.jar with the current one from the project homepage (it has a different name, put in in there as Azureus2.jar). This, too, I would not have expected.

Apart from that, nothing special so far :-)

Friday, April 20, 2007

Something to know about Toshiba external USB HDD

As I wrote before, luckily I bought an external hard disk and started backing up my data just in time. Well, although this may look like talking bad about good things, I still believe I should post this. The drive model I bought is a Toshiba PX1269E-G50, a 500GB external USB 2.0 model.

On the box it says this:

Optimized for digital video, photos and music storage. Fanless design for near-silent operation. Interface: high-speed USB 2.0. Interface transfer rate (max.): 480 Mbps. Rotational speed: 7200 rpm. Cache: 16 MB. PushButton Backup. Password security. System requirements: PC with minimum 233 MHz pentium or equivalent (Celeron, AMD, etc.), Windows 2000/XP, available USB 2.0 port.

I read this before buying in the store. I did not care for whatever "PushButton Backup" is. I still don't. I did not care for Password security. I still don't. It says Windows 2000/XP, but after all it is a USB mass storage device. Not needing the nifty Windows backup or security tools I decided to buy it anyway, because it was the only one available with this amount of space.

At home I hooked it up to my Ubuntu box via USB and was somewhat surprised to see a CD-ROM drive being detected and pmount'ed to my desktop. The disc title "PASSWORD" raised my suspicion. This was the moment I decided to have a look at the manual (hey, it's a hard drive, so why include one in the first place?). I almost fell from my chair when I read this:

Your Toshiba Drive came from the factory with Password Security enabled, and programmed with a pre-set password. The first time you connect the Drive to your computer, the login screen will appear. If you want to keep Password Security enabled, follow the instructions below to change the pre-set password. If you want to disable Password Security, go to "Disabling Password Security" on the next page.

Can you believe this? This thing actually reports two drives to the computer - the first being a CD-ROM drive containing some PDFs and driver software and this security thingie. Only for Windows of course. One of the documents on that virtual CD elaborates more on the nature of this "security feature":

You can enable or disable Password Security at any time. Doing so will not affect any data stored on your Drive. Follow the instruction below to enable Password Security for an unsecured Drive. [...] If you permanently forget your password, you will not be able to access the Drive, and any data stored on the Drive will effectively be lost. [...] Password Security allows three consecutive attempts to enter your password at login. If the third attempt is unsuccessful, you will be able to view your Hint, and you will have one last chance to enter your password. [...] If you permanently forget your password you will not be able to access the Drive. For all intents and purposes, the Drive will be rendered unusable, and any data stored on the Drive will effectively be lost. If you want to regain the use of your Drive, you will need to make arrangements to return the Drive to the factory, where the CD+Secure HDD partitions will be deleted and re-created. You will not recover your data, but at least the Drive will be functional again.

I especially like the "for all intents and purposes" part. Had I not already stored my data on the disk (after using the otherwise unused Windows XP I still have to disable the password check and make it appear under Linux as a hard drive at all) I would have brought it back to the store. I probably would have, even had I used Windows. After all, who guarantees that this probably low-level stuff on that inaccessible CD-ROM simulation partition will work on future Windows versions? Just imagine having set up a password on the disk, then upgrading your XP to the all-new shiny Vista just to find out that the driver is incompatible and you don't get your hands on your own files?

Well... Now if you can live with a CD drive appearing needlessly when you plug in a hard drive, and you either disable the password thing altogether (which I totally recommend) or understand the possible consequences, go ahead and buy it. From my experience it is a least blazingly fast. Otherwise: Choose another product.

One more thing: I looked around on the net and found someone who disassembled the whole thing, put the hard drive itself into a PC and removed the CD partition. After putting it back into the external case it would not appear anymore at all. My guess is that either the firmware of the controller has some sort of protection against this, is buggy or maybe even in part contained on the disk itself. Anyways, I just thought I'd put this here for you to find before you ruin yours in the attempt.

Monday, April 16, 2007

FindBugs - Writing custom detectors (Part 1)

Some words in advance...

Recently I wrote about multi-threading problems with java.util.Calendar and java.text.DateFormat. The last sentence was So maybe it is time to search your code for all static usages of the Calendar and various ...Format classes, before you start getting strange errors.

Searching code is not very practical, especially if you do it manually. Everyone knows you can look at code for hours, without seeing an problem - and as soon as it has reached production systems it starts breaking up in various ways :-)

Fortunately smart and reknown people have devised ways of making the computer look for bugs automatically. Amongst others, FindBugs is a very nice - and free - tool that analyzes your Java application's compiled bytecode and looks for numerous so called bug patterns. Those patterns are divided into categories, such as "Bad practice", "Correctness", "Multithreaded correctness", "Performance" and some more. Each of them looks for a specific kind of error people tend to make in their programs. While there are a lot (close to 300 if I remember correctly) of patterns included with the release you can download, not every bug you find will be covered by one of them. Luckily, as FindBugs is free software, you can add you own "detectors" to it if you encounter something you believe is lurking around in your (and other people's) code.

In case you are completely knew to FindBugs or the idea of static code analysis in general, go have a look at a Google Tech Talk by Bill Pugh, one of the minds behind FindBugs. You should then go and download the latest version and run it against you code. You will be amazed on what (sometimes stupid) mistakes you will find.

Because I had some problems finding a good howto on writing bug detectors, I decided to write one myself. The only (hands on, step by step) source I found was a tutorial on developers.net. When you read on, you will probably find some parallels to that article, however I believe it is good to have one more example. After I started writing I decided to split the tutorial in two, because it was growing too long. This first part is about detecting the declaration of static Calendar or DateFormat fields. The second will be about finding invocations on static instances, which is a little bit trickier. For the impatient, you can go grab the latest trunk revision from the FindBugs repository on Google Code. You will find the my detector there, because to my pleasure it was included in the official distribution. It will probably be deactivated by default in the upcoming 1.2.0 release, because it was already in the release candidate stages when I submitted my patch. You will be able to try it out, however I will use the time and try and improve it a little more.

The howto...

But now, let's get to it.

Something to think about

Imagine you want to write a simple utility class that allows you to format java.util.Date objects to a specific display format. java.text.SimpleDateFormat is ideal for the job. Because the format is going to be the same for all dates you pass to the getFormattedDate() method, you employ a static instance of the formatter to save some resources. To make sure everything works a you expect, you use some assertions:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;


public class BadCalendar {
    /** common formatter, displays just the time */
    static SimpleDateFormat formatter = new SimpleDateFormat("HH-mm-ss");
    
    /** 
     * Format the given date according to the predefined pattern.
     * @param aDate the date to be formatted (just the time is used)
     * @return formatted time String
     */
    public static String getFormattedDate(Date aDate) {
        return formatter.format(aDate);
    }

    public static void main(String[] args) {
        String formatted;
        Calendar cal = Calendar.getInstance();
        
        cal.set(2007, Calendar.JANUARY, 01, 01, 01, 01);
        formatted = getFormattedDate(cal.getTime());
        assert formatted.equals("01-01-01");
        
        cal.set(2007, Calendar.JANUARY, 01, 01, 01, 02);
        formatted = getFormattedDate(cal.getTime());
        assert formatted.equals("01-01-02");
        
        cal.set(2007, Calendar.JANUARY, 01, 01, 01, 03);
        formatted = getFormattedDate(cal.getTime());
        assert formatted.equals("01-01-03");
    }
}

Running this program will yield no result, i. e. the assertions are ok (remember you have to use the -enableassertions parameter to have them actually executed. You change one of the assertions so that it must fail, to verify they are working.). You might add more test cases to make absolutely sure that there is no bug lurking in this code. You may in fact be perfectly comfortable that this class is bug free in itself.

Now, as we now know that the formatting works, let's put it to some use:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;


public class BadCalendar2 {
    static SimpleDateFormat formatter = new SimpleDateFormat("HH-mm-ss");
    
    
    public static String getFormattedDate(Date aDate) {
        return formatter.format(aDate);
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Worker worker = new Worker();
            worker.setName("Thread " + i);
            worker.start();
        }
    }
}


class Worker extends Thread {
    private Random random = new Random();
    
    public void run() {
        long quitBy = System.currentTimeMillis() + 20000L; // in 20s
        while (System.currentTimeMillis() < quitBy) {
            // 1970-01-01 < date < 1970-01-02
            Date date = new Date(random.nextInt(86400000));
            String result = BadCalendar2.getFormattedDate(date);
            try {
                // simulate some real work of varying length
                System.out.println(getName() + " working..." + result);
                Thread.sleep(random.nextInt(500));
            } catch (InterruptedException e) {
                ; // ignore
            }
        }
    }
}

The Worker class in this example simulates some work that takes between 0 and 500ms. Part of this work is to print a random timestamp, formatted using the getFormattedDate() method from BadCalendar. When you run it, it will work for 20 seconds and then finish. The output looks like this:

...
Thread 0 working...14-51-02
Thread 6 working...18-08-00
Thread 7 working...01-38-54
Thread 8 working...04-46-19
Thread 9 working...04-59-00
Thread 4 working...13-16-49
Thread 8 working...05-00-37
Thread 7 working...18-43-29
Thread 2 working...15-51-10
Thread 5 working...11-52-46
Thread 0 working...05-23-37
Thread 4 working...03-39-22
...

Looks good, doesn't it? Nicely formatted time values. However if you were to use this kind of date formatting utility in a real world application you might get weird error reports from your customers, complaining about times they never entered or even some random, not reproducible ArrayIndexOutOfBoundsExceptions. While the first part of this might be explained by mistakes users make entering dates they shouldn't be able to produce the exceptions in this code, should they?

Having a closer look at the SimpleDateFormat JavaDoc reveals this paragraph:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

Now that sounds suspicious. Usually you wouldn't read the JavaDoc of a class so simple and so well-known (at least I did not). So maybe we can verify if we have a multi-threading problem by double-checking the values the formatter returns:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;


public class BadCalendar2 {
    static SimpleDateFormat formatter = new SimpleDateFormat("HH-mm-ss");
    
    
    public static String getFormattedDate(Date aDate) {
        return formatter.format(aDate);
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Worker worker = new Worker();
            worker.setName("Thread " + i);
            worker.start();
        }
    }
}


class Worker extends Thread {
    private Random random = new Random();
    
    public void run() {
        long quitBy = System.currentTimeMillis() + 20000L; // in 20s
        while (System.currentTimeMillis() < quitBy) {
            // 1970-01-01 < date < 1970-01-02
            Date date = new Date(random.nextInt(86400000));
            String result = BadCalendar2.getFormattedDate(date);
            SimpleDateFormat myDateFormat = new SimpleDateFormat("HH-mm-ss");
            String myResult = myDateFormat.format(date);
            try {
                // simulate some real work of varying length
                if (myResult.equals(result)) {
                    System.out.println(getName() + " working... ok:" + result);
                } else {
                    System.out.println(getName() + " working... fail: " + result + " != " + myResult);
                }
                       
                Thread.sleep(random.nextInt(500));
            } catch (InterruptedException e) {
                ; // ignore
            }
        }
    }
}

Running this will turn out something like:

...
Thread 9 working... ok:05-58-52
Thread 7 working... ok:02-49-29
Thread 2 working... ok:09-14-50
Thread 0 working... ok:07-27-27
Thread 6 working... fail: 07-27-27 != 03-01-43
Thread 5 working... ok:21-37-10
Thread 0 working... ok:00-10-40
Thread 5 working... ok:12-32-35
Thread 2 working... fail: 12-32-35 != 11-34-26
Thread 3 working... ok:05-16-31
Thread 0 working... ok:16-46-49
Thread 9 working... ok:16-39-35
Thread 8 working... ok:23-42-52
Thread 7 working... ok:07-39-06
Thread 6 working... ok:11-23-54
Thread 5 working... ok:04-24-50
Thread 1 working... ok:07-55-40
...

So we indeed get different results from what we would expect. Looking closer at the implementation of SimpleDateFormat reveals that it uses a Calendar internally. As those are not thread-safe (even though their documentation does not mention this, see Sun Bug #6178997) we get messed up results. So it's clearly good advise not to share the static instance of the formatter across thread boundaries. Instead, one should use an instance per Thread or at least synchronize access to the shared one.

What to do?

This is a very subtle kind of bug, nothing you will find just by reading the code. It looks perfectly alright unless you or someone in your team took a closer look at the documentation and knew about this particular issue. But there are different things and you will come across one that neither you nor someone near you has seen before. And it is very likely to make the same mistake again, perhaps not now, but in a few weeks or months. Tracking the problem down will be difficult again.

People's minds have a somewhat limited capacity, and people have limited time to look for errors - they are needed to get the programming done. What about leaving the recurring, tedious tasks to someone - something - that can do it better and faster? Enter code analysis software, e. g. FindBugs.

As far as I could see, there was no particular bug pattern detector for this special kind of problem in the downloadable FindBugs package. Creating a new one requires some knowledge about the byte code level and what to look for in it. The problem at hand could be described like this:

  • a static field of type java.util.Calendar or a subclass thereof
  • a static field of type java.text.DateFormat or a subclass thereof
  • method calls on static instances of java.util.Calendar or a subclass thereof
  • method calls on static instances of java.text.DateFormat or a subclass thereof

The general idea of a detector is to find certain patterns in the bytecode of a class. To do so it will read the .class files and put them through pattern matchers that are implemented using the visitor pattern. While reading it will call appropriate visitor methods, depending on what element (method, field declaration etc.) is at hand. Writing a detector means implementing one or more of those methods, building up an idea about what the class is intended to do. The framework behind this is the Apache Bytecode Engineering Library.

Looking at some of the existing detectors can help to get an idea about the concept, however I found it easier to start fresh with an initially empty subclass of edu.umd.cs.findbugs.BytecodeScanningDetector. First thing to override is the visit(Field) method - after all this is to find field declarations.

@Override
public void visit(Field aField) {
    super.visit(aField);
}

Upon visiting a field there are some things to be done:

  • Check if the field is declared static, otherwise it is of no interest. This is easily done: The Field object that is passed into the visit method knows about this.
  • Check if the field's type is an object (in contrast to a primitive int, for example). This is also easy: the Field can tell its type.
  • If it is an object, check if it's defined as a Calendar/DateFormat or a subclass. This needs something to compare to. There is an ObjectTypeFactory that can be used to create an appropriate object for Calendars and DateFormats.
  • If all of this is true, issue a warning, otherwise ignore the field

This is what code looks like:

import java.text.DateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ObjectType;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.ba.ObjectTypeFactory;


/**
 * Detector for static fields of type {@link java.util.Calendar} or
 * {@link java.text.DateFormat} and their subclasses. Because {@link Calendar}
 * is unsafe for multithreaded use, static fields look suspicous. To work
 * correctly, all access would need to be synchronized by the client which
 * cannot be guaranteed.
 * 
 * @author Daniel Schneller
 */
public class StaticCalendarDetector extends BytecodeScanningDetector {

    /** External Debug flag set? */
    private static final boolean DEBUG = Boolean.getBoolean("debug.staticcal");

    /** The reporter to report to */
    private BugReporter reporter;

    /** Name of the class being inspected */
    private String currentClass;

    /** {@link ObjectType} for {@link java.util.Calendar} */
    private final ObjectType calendarType = ObjectTypeFactory.getInstance("java.util.Calendar");

    /** {@link ObjectType} for {@link java.text.DateFormat} */
    private final ObjectType dateFormatType = ObjectTypeFactory.getInstance("java.text.DateFormat");

    /** Constant for types of fields */
    private static final int TYPE_UNDEFINED = 0;

    /** Constant for types of fields */
    private static final int TYPE_CALENDAR = 1;

    /** Constant for types of fields */
    private static final int TYPE_DATEFORMAT = 2;


    /** ProgramCounter of the last seen  {@link org.apache.bcel.generic.GETSTATIC} call to a {@link Calendar} */
    private int seenStaticGetCalendarAt;

    /** ProgramCounter of the last seen  {@link org.apache.bcel.generic.GETSTATIC} call to a {@link DateFormat} */
    private int seenStaticGetDateFormatAt;

    /**
     * Map of stores of a static {@link Calendar} instance to a register.
     * Keys: Registers, Values: ProgramCounters
     */
    private Map<Integer, Integer> registerStaticStoreCalendarAt = new HashMap<Integer, Integer>();

    /**
     * Map of stores of a static {@link DateFormat} instance to a register.
     * Keys: Registers, Values: ProgramCounters
     */
    private Map<Integer, Integer> registerStaticStoreDateFormatAt = new HashMap<Integer, Integer>();

    /**
     * Remembers the class name
     */
    @Override
    public void visit(JavaClass someObj) {
        currentClass = someObj.getClassName();
        super.visit(someObj);
    }

    /**
     * Checks if the visited field is of type {@link Calendar} or
     * {@link DateFormat} or a subclass of either one. If so and the field is
     * static it is suspicious and will be reported.
     */
    @Override
    public void visit(Field aField) {
        super.visit(aField);
        if (!aField.isStatic()) return; // exit early on non-static fields
        Object fieldtype = aField.getType();
        if (!(fieldtype instanceof ObjectType)) return; // exit early on non-object fields

        ObjectType objecttype = (ObjectType)fieldtype;
        int tTyp = TYPE_UNDEFINED;
        try {
            if (objecttype.subclassOf(calendarType)) {
                tTyp = TYPE_CALENDAR;
            } else if (objecttype.subclassOf(dateFormatType)) {
                tTyp = TYPE_DATEFORMAT;
            }
        } catch (ClassNotFoundException e) {
            ; // ignore
        }

        String typeForMessage;
        switch (tTyp) {
        case TYPE_CALENDAR:
            typeForMessage = "STCAL_STATIC_CALENDAR_INSTANCE"; // as in findbugs.xml
            break;
        case TYPE_DATEFORMAT:
            typeForMessage = "STCAL_STATIC_SIMPLE_DATE_FORMAT_INSTANCE"; // as in findbugs.xml
            break;
        default:
            typeForMessage = null;
            break;
        }
        if (typeForMessage != null) {
            reporter.reportBug(new BugInstance(this, typeForMessage, NORMAL_PRIORITY).addClass(currentClass).addField(
                    currentClass, aField.getName(), aField.getSignature(), true));
        }
    }

This is everything that needs to be coded to implement the detection of static fields of one of the types we are interested in. In order to include this in a FindBugs sweep it needs to be packaged as a FindBugs plugin. This requires two more files: findbugs.xml and messages.xml. They contain some meta information about the check (like which category it belongs to) and the messages that should be printed when the detector finds something.

findbugs.xml:

<FindbugsPlugin>
 <Detector class="de.bismarck.findbugs.checks.StaticCalendarDetector" speed="fast" />
 <BugPattern abbrev="STCAL" type="STCAL_STATIC_CALENDAR_INSTANCE" category="MT_CORRECTNESS" />
 <BugPattern abbrev="STCAL" type="STCAL_STATIC_SIMPLE_DATE_FORMAT_INSTANCE" category="MT_CORRECTNESS" />
</FindbugsPlugin>



messages.xml:

<MessageCollection>
<Detector class="de.bismarck.findbugs.checks.StaticCalendarDetector">
<Details>
<![CDATA[
<p>This detector warns about static fields of type java.util.Calendar or java.text.DateFormat (and subclasses) because Calendars are inherently unsafe for multithreaded use. It is a fast detector.</p>
]]>
</Details>
</Detector>

<BugPattern type="STCAL_STATIC_CALENDAR_INSTANCE">
<ShortDescription>Static Calendar</ShortDescription>
<LongDescription>Found static field of type java.util.Calendar in {1}</LongDescription>

<Details>
<![CDATA[
<p>Even though the JavaDoc does not contain a hint about it, Calendars are inherently unsafe for multihtreaded use. Sharing a single instance across thread boundaries without proper synchronization will result in erratic behavior of the application. Under 1.4 problems seem to surface less often than under Java 5 where you will probably see random ArrayIndexOutOfBoundsExceptions or IndexOutOfBoundsExceptions in  sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate().</p>

<p>You may also experience serialization problems.</p>
<p>For more information on this see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6231579">Sun Bug #6231579</a>
and <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6178997">Sun Bug #6178997</a>.</p>
]]>

</Details>
</BugPattern>

<BugPattern type="STCAL_STATIC_SIMPLE_DATE_FORMAT_INSTANCE">
<ShortDescription>Static DateFormat</ShortDescription>
<LongDescription>Found static field of type java.text.DateFormat in {1}</LongDescription>
<Details>
<![CDATA[
<p>As the JavaDoc states, DateFormats are inherently unsafe for multithreaded use. 
Sharing a single instance across thread boundaries without proper synchronization will result in erratic behavior of the application.</p>
<p>You may also experience serialization problems.</p>
<p>For more information on this see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6231579">Sun Bug #6231579</a>
and <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6178997">Sun Bug #6178997</a>.</p>
]]>
</Details>
</BugPattern>

<BugCode abbrev="STCAL">Static use of type Calendar or DateFormat</BugCode>
</MessageCollection>

The detector class and the XML files must be packaged into a jar file. This can be put into the plugins directory of your FindBugs installation. This is all thre is to it to activate the detector. I recommend launching FindBugs from a command line window (using java.exe instead of javaw.exe on Windows) to see any error messages that might prevent your plugin from loading.

The second part of this tutorial will be about enabling the detector to find invocations of methods on static instances of the thread-unsafe classes. Stay tuned.

Sunday, April 15, 2007

Backup... Just in time

Even though I intended to write about something completely different (the first FindBugs bug detector I wrote), I decided to put this first, because of it having been more "important" to me in the last week.

Some background: About three years ago my girlfriend's notebook drive crashed. Even though she insists it was my fault, because I was using the computer at the time, it was the first hard drive in my immediate surroundings that actually had a real hardware defect. Call me lucky, but in all the time back to my first HD (must have been around 1992) I never really saw a broken hard drive.

Back then I was concerned about my own data and started backing up my files to DVDs. However I - as probably anyone does - soon grew tired of burning new disks, because the time never seemed to be right. Either I had not really changed too much, or there was simply no time to do it. So soon I forgot about it again.

Three weeks ago I heard about from a colleague that in one weekend he had lost all three of his computers at home (one PC, two Macs). One had a "simple" hard drive crash, the next one's PSU decided to incinerate itself, sending the full 230V through the machine's components, leaving little to salvage. The last machine just stopped working, but I do not really know why.

Not really succeeding to remember when I had last burned my more important data to DVDs (burning all of it including music and videos is just too impractical - and expensive - to do) I decided that I had to do something about my backup strategy - given that what I had done before even granted calling it that.

Last Saturday I bought a 500GB external USB hard drive. German computer magazine c't (some English pages here) presented an rsync based Visual Basic Script for data backups to external disks in 2006 (see article and the script). Even though the script it designed to be used on Windows and NTFS formatted backup disks still found the idea very interesting: Make one full backup and do incremental backups utilizing rsync's ability to create hard-links for files it finds in a previous backup. This effectively lets you see multiple backup directories on the backup drive, each of them virtually presenting a full backup, but at only the disk space cost of incremental backups. A quick search of the new brought me to www.rsnapshot.org/, a free utility written in perl and based on the very same principle.

I downloaded and installed the Ubuntu package and set it up to backup much more than I had previously burned to DVDs manually. In total it copied around 300GB to the external disk in the first run. The day after that (Sunday) I let it run again (have set up cron now). The second folder contains roughly the same amount of data, but the free space on the drive has not really been decreased. Great stuff :-)

Fast forward to Thursday - three more backup folders on the external drive - virtually the same free space. Editing my FindBugs blog post I see the external disk starting to work - daily backup run just started. A few minutes later I start to wonder why I hear strange noises from my PC. Looking a dmesg's output I see lots this:

Apr 12 03:22:52 yavin kernel: [17197689.492000] ata2: status=0x51 { DriveReady SeekComplete Error }
Apr 12 03:22:52 yavin kernel: [17197689.492000] ata2: error=0x40 { UncorrectableError }
Apr 12 03:22:54 yavin kernel: [17197690.604000] ata2: status=0x51 { DriveReady SeekComplete Error }
Apr 12 03:22:54 yavin kernel: [17197690.604000] ata2: error=0x40 { UncorrectableError }
Apr 12 03:22:54 yavin kernel: [17197690.604000] sd 1:0:0:0: SCSI error: return code = 0x8000002
Apr 12 03:22:54 yavin kernel: [17197690.604000] sdb: Current: sense key: Medium Error
Apr 12 03:22:54 yavin kernel: [17197690.604000]     Additional sense: Unrecovered read error - auto reallocate failed
Apr 12 03:22:54 yavin kernel: [17197690.604000] Info fld=0xbce4016
Apr 12 03:22:54 yavin kernel: [17197690.604000] end_request: I/O error, dev sdb, sector 198066192
Apr 12 03:23:47 yavin kernel: [17197744.520000] ata2: status=0x51 { DriveReady SeekComplete Error }
Apr 12 03:23:47 yavin kernel: [17197744.520000] ata2: error=0x40 { UncorrectableError }
Apr 12 03:23:49 yavin kernel: [17197745.640000] ata2: status=0x51 { DriveReady SeekComplete Error }
Apr 12 03:23:49 yavin kernel: [17197745.640000] ata2: error=0x40 { UncorrectableError }
Apr 12 03:23:50 yavin kernel: [17197746.756000] ata2: status=0x51 { DriveReady SeekComplete Error }
Apr 12 03:23:50 yavin kernel: [17197746.756000] ata2: error=0x40 { UncorrectableError }
Apr 12 03:23:51 yavin kernel: [17197747.872000] ata2: status=0x51 { DriveReady SeekComplete Error }
Apr 12 03:23:51 yavin kernel: [17197747.872000] ata2: error=0x40 { UncorrectableError }
Apr 12 03:23:52 yavin kernel: [17197748.980000] ata2: status=0x51 { DriveReady SeekComplete Error }

Long story short: One of my hard drives, a Samsung Spinpoint SP1614C (160GB SATA), suddenly died - only a few days after I had backed up everything on it...

When did you last back up your stuff?

Thursday, April 12, 2007

BumpTop 3D Desktop

A colleague pointed me to a very interesting website about a project that aims to enhance the desktop experience by utilizing state of the art 3D technologies: http://www.bumptop.com. The basic idea is Enriching the Desktop Metaphor with Physics, Piles and the Pen. Go to the site to read more, they provide some papers about their work and offer to subscribe to a mailing list.

For the impatient: Here is the "official" YouTube demonstration:

Friday, April 06, 2007

Calendar, DateFormat and multi threading

Another anecdote from the ongoing Java 1.4 to Java 5 transition... Last time it was an incarnation of "Favor composition over inheritance". This time I am writing about a multi-threading issue which can be equally subtle and difficult to pinpoint.

On the test systems we have set up we observed two strange types of exceptions at seemingly random times and in various different areas of the application. They looked similar to the following two patterns:

  • java.lang.ArrayIndexOutOfBoundsException: 432
            at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436)
            at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2024)
            at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:1941)
            at java.util.Calendar.setTimeInMillis(Calendar.java:1066)
            at java.util.Calendar.setTime(Calendar.java:1032)
            ... (our classes)
    
  • java.lang.ClassCastException: java.util.SimpleTimeZone
     at java.util.Calendar.writeObject(Calendar.java:1643)
     at sun.reflect.GeneratedMethodAccessor500.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
     at java.lang.reflect.Method.invoke(Method.java:324)
     at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:795)
     at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1294)
     at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1245)
     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1052)
     at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1330)
     at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1302)
     at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1245)
     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1052)
     at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1330)
     ... (more of this)
     ... (our classes)
    

Those errors, especially the first one, would suddenly happen at a seemingly random place. Re-running the program with the same inputs on the same machine would sometimes complete normally and sometimes crash at a different place.

Searching for the sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436) line brought me to Java Bug #6231579 which describes exactly our problem. The bug has been closed and classified as "not a bug", because they say that Calendar was not designed to be thread-safe and that suchlike behavior can be expected when sharing an instance over several threads. This is exactly what we found in our product: In a utility class we used two static final java.util.Calendar instances that were shared among all threads using the utility. Interestingly Calendar's javadoc does not mention the unsafe nature of the Calendar class. Alright, thinking about it in retrospective sharing them was not such a good idea after all, because one could have guessed that there might be problems. So changing this piece of code was rather easy, once it had been found.

However SimpleDateFormat's documentation includes a hint about synchronization issues. This one is even more important, because there are probably many more people using some shared instances of those and get bitten. There is a series of bugs reported about this, one of them being Bug #4264153.

So maybe it is time to search your code for all static usages of the Calendar and various ...Format classes, before you start getting strange errors.

Wednesday, April 04, 2007

Composition over Inheritance... again

After what seemed like forever we have now finally begun to migrate our product from Java 1.4 to Java 5. Java 6 would have been nicer, but that is another story.

The first step was of course to switch compilers, runtime environment and Eclipse's source compliance settings. What followed were the usual suspects before we could compile again (uses of e. g. enum as a variable name, different package names in the XML area that bit us because of not using Factories etc).

However one thing did go unnoticed and was only found some days on a test system because of strange data-truncation warnings. For some reason negative numbers were too long by 1 digit. To understand this one has to know about a rather old data exchange format we need to support. It's string-based with leading zeros for numbers. Interestingly you can send a number like 99999 (positive), but only -9999 (negative), because there is a maximum length of 5 characters in the format.

The JDK's DecimalFormat almost does the job, but it will put a minus sign in front of the leading zeros, making the format too long by one character for negative numbers:

DecimalFormat tFormat = new DecimalFormat("00000") ;
// this is what it does
assert "99999".equals(tFormat.format(new BigDecimal("99999")));  // ok        
assert "-09999".equals(tFormat.format(new BigDecimal("-9999"))); // ok
assert "-00001".equals(tFormat.format(new BigDecimal("-1")));    // ok
// but this is what we want
assert "-9999".equals(tFormat.format(new BigDecimal("-9999")));  // fail
assert "-0001".equals(tFormat.format(new BigDecimal("-1")));     // fail

To achieve the desired behavior, one of the developers introduced a CustomNumberFormat class. This is it:

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.util.Locale;

class CustomNumberFormat extends DecimalFormat {

    private int formatLength;

    private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols(
            Locale.US);

    public CustomNumberFormat(String aFormat) {
        super(aFormat, SYMBOLS);
        formatLength = aFormat.length();
    }

    public StringBuffer format(double aNumber, StringBuffer aBufferToAppend,
                               FieldPosition aPos) throws NumberFormatException {
        StringBuffer tempResult = super.format(aNumber, aBufferToAppend, aPos);
        if (tempResult.length() >= 2 && tempResult.substring(0, 2).equals("-0")) {
            tempResult.deleteCharAt(1);
        }
        if (tempResult.length() > formatLength) {
            throw new NumberFormatException();
        }
        return tempResult;
    }

    public String formatNumber(BigDecimal aNumber) {
        if (aNumber == null) {
            return super.format(new BigDecimal("0"));
        }
        return super.format(aNumber);
    }

}

Apart from providing a convenience formatNumber(BigDecimal) method the most striking thing that you may notice is the overriden format(double, ...) method. Why would you override a method with a signature requiring a double value? Well, it turns out, that any other useful method in the super class is final or private, so no help there. However looking at the source of DecimalFormat reveals that calling the DecimalFormat.format(Object) method leads to this call-stack:

CustomNumberFormat.format(double, StringBuffer, FieldPosition) line: 22 
CustomNumberFormat(NumberFormat).format(Object, StringBuffer, FieldPosition) line: 216 
CustomNumberFormat(Format).format(Object) line: 133 

The second line is most interesting: It turns out that for BigDecimals the format(double, ...) gets called. Because this is overriden, the version in CustomNumberFormat is executed. It first calls super.format(double,...) and then removes the charAt(1) of the result, which must be a "0" if it starts with "-0". In case the number was too big, a NumberFormatException is thrown.

This achieves our goal:

 
DecimalFormat tFormat = new CustomNumberFormat("00000") ;
// this is what it does *and* what we want
assert "99999".equals(tFormat.format(new BigDecimal("99999")));  // ok        
assert "-9999".equals(tFormat.format(new BigDecimal("-9999"))); // ok
assert "-0001".equals(tFormat.format(new BigDecimal("-1")));    // ok

So far, so good, job done.

Enter Java 5.

Running the same code again - this time just for one example - leads to this:

DecimalFormat tFormat = new CustomNumberFormat("00000") ; 
String tResult = tFormat.format(new BigDecimal("-1"));
assert "-0001".equals(tResult): tResult; // java.lang.AssertionError: -00001

For some reason now the output is too long by one again. Great... must be a Java 5 bug, must it not?

Of course it is not. It is just the good old "Favor composition over inheritance" rule violated and showing its ugly face. The implementation of CustomNumberFormat relies on the execution path running through the format(double,...) method, i. e. on the concrete implementation of this runtime class library. Looking at the Java 5 source reveals that this is no longer the case for BigDecimals. Because of this, the overriden method never gets executed, effectively making it useless. Even worse: In a different scenario it might get called unintentionally, depending on how the implementation in the Java 5 class library was modified.

So, to solve the problem, just do as Josh Bloch tells you in Item 14 (link above): Favor composition over inheritance, i. e. do not extend DecimalFormat but simply use an instance of it like so:

 
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class NewCustomNumberFormat {

    private DecimalFormat decimalFormat;

    private int formatLength;

    private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols(
            Locale.US);

    public NewCustomNumberFormat(String aFormat) {
        decimalFormat = new DecimalFormat(aFormat, SYMBOLS);
        formatLength = aFormat.length();
    }

    public String format(Object aNumber) throws NumberFormatException {
        String tFromDecimalFormat = decimalFormat.format(aNumber);
        StringBuffer tempResult = new StringBuffer(tFromDecimalFormat);
        if (tempResult.length() >= 2 && tempResult.substring(0, 2).equals("-0")) {
            tempResult.deleteCharAt(1);
        }
        if (tempResult.length() > formatLength) {
            throw new NumberFormatException();
        }
        return tempResult.toString();
    }

    public String formatNumber(BigDecimal aNumber) {
        if (aNumber == null) {
            return format(new BigDecimal("0")).toString();
        }
        return format(aNumber).toString();
    }

}

While the semantics are the same, this class actually works in Java 1.4 and Java 5 and will most certainly continue to do so in later versions as well. Notice that this class does not extend anything, but merely utilizes an instance of DecimalFormat to delegate the formatting to, before cutting away one of the zeros, making it completely independent of DecimalFormat's implementation details.

While this is probably old news to many, the remaining few are probably worth the post :)

Sunday, April 01, 2007

Google Code Prettify

While working on a completely different post regarding subtle bugs surfacing in a Java 1.4 to 5 migration, I asked myself whether there was simple way of getting code samples to be syntax-highlighted on my blog. Pure CSS would not work, unless the code samples were completely messed up with spans and divs to indicate which style to apply to every word, number or punctuation.

This could have been done with tools like Java2Html, but it generates lots of font tags instead of CSS based style instructions.

Looking around I found Google-Code-Prettify, a combined JavaScript and CSS solution to my problem. It turns out to be very easy to use, you just have to specify a class="prettyprint" to your pre blocks and activate the JavaScript via the body tag's onLoad hook. It will then try and guess which language any "prettyprinted" pre block contains and format it according to a stylesheet you may modify to fit into your site's layout. The number of languages recognized is impressive, even though I would have been satisfied with Java alone :)

I will probably go back though my posts that contain code samples and update them. New posts will of course be "prettified" right from the start.