|
|
|
Back to newsletter 075 contents | All Javva's articles
Javva The Hutt is on holiday. Instead, his colleague, Hutt5, writes about his tribulations in upgrading to Java 5.
Our project upgraded to Java 5 recently. We thought we did it carefully and thoroughly, but we still got hit by a number of gotchas - all of our own making I should say, but I suspect a good proportion of you out there may well have some of the same kinds of "bad" practices that caused our upgrade to go less than smoothly, and Javva suggested I write down the problems we encountered so that maybe you can avoid them.
I work on a large project. We have thousands of classes we've developed, and thousands of third-party classes (mostly open source ones, but some vendor specific too). Over the years, we've probably had hundreds of programmers touch the code in many places. In this kind of project, I think its pretty easy for mixed quality code to sneak through into the project. I can say up front that bad practice number one is that we haven't really enforced coding standards using any tool. We do have a set of coding standards, and also code checking tools that are run over the code, but its clear from the problems we encountered that we haven't really integrated the two, and haven't enforced the tool output warnings. And all that didn't help our upgrade.
Anyway, we started our migration to Java 5 with several versions of our project to spare before the target deployment version. We had a migration plan, first to resolve incompatibilities from running the project under a Java 5 JVM; then to run the project under the Java 5 JVM but using our existing pre-Java 5 compiler; then to compile under Java 5 and run under Java 5, the last step so that we could start using Java 5 code features. (We also analysed the Java 5 new features for best practices and those to avoid, and came up with internal recommendations, but this aspect probably isn't all that relevant - though you may like to know that generics has been very mixed in benefits, making the code both more understandable in some places, and much less readable in others).
Step 1 was running the existing project under the Java 5 JVM, and that threw up a few incompatibilities. Primarily there were some XML processing related classes that we were using that had been migrated into the Java 5 core classes - they were easy to identify and easy to fix. It looked like our Java 5 migration was going really well. Pushing the new configuration with a Java 5 JVM into QA revealed bad practice number two: we were using the toString() method of one of the Java core classes to transfer data from one of our components, and that toString() method had changed to produce a different output format. I suspect we may be reliant on the format of other toString() methods, but fortunately only one caused problems. Still, none of this was a huge problem, and we were deployed on the Java 5 JVM without too much trouble - and that meant that were able to start adding Java 5 flags like -XX:+HeapDumpOnOutOfMemoryError, which is pretty useful.
Step 2, compiling using the Java 5 source compiler was where the bigger problems came, revealing that we had been following bad practices 3, 4 and 5. Bad practice 3 was that we were serializing anonymous inner classes. It wasn't a conscious decision, it was just some objects that had crept in to the serialization network by lack of coding standards control. Like the other bad practices, it's known that this can lead to problems - the serialization spec actually says "Note Serialization of inner classes (i.e., nested classes that are not static member classes), including local and anonymous classes, is strongly discouraged" and goes on to explain several reasons why it's discouraged including that "the names assigned to local and anonymous inner classes are also implementation dependent and may differ between compilers". As it turns out, the Java 5 compiler changes the way it generates names of inner classes to fix a bug it has. And we hit that change. More serious than the previous problems we'd seen, this required us to identify all the generated class names that this affected (by comparing the names of all the .class files generated by the Java 5 compiler and our previous compiler). Then we had to fix the problem, migrate the objects and make sure we deployed the new class versions to all clients and servers simultaneously - never something we want to do because in the case of a problem we have to apply a rollback across the whole system, which means downtime across the whole system instead of isolated components.
Hot on the heels of bad practice number 3 came bad practice 4: that we hadn't explicitly defined serialVersionUID for all serializable classes. This was another of our coding standards that we hadn't applied consistently, even with existence of a specific tool to check for this. It's so easy to subclass without giving it a thought. And, of course, as we found, Java 5 has a tweaked the serialVersionUID generator, so the generated serialVersionUIDs are different for some classes. Which meant we hit more serialization incompatibilities. Analysing the whole universe of classes that were listed in all our jars, listed twelve thousand classes that were Serializable but that didn't have a serialVersionUID defined. Fully half of these had different serialVersionUIDs generated from 1.4 and 1.5! So far, all of this was caught in QA, and though not causing production issues, they were increasing the amount of work that needed to be applied to get the project fully deployed using Java 5.
The last gotcha, bad practice number 5, did get through to production. First I'll explain the background to this bad practice. In Java 5, classes have become literals. Prior to Java 5, the clause "XYZ.class" where XYZ is any class, would cause the compiler to replace that clause with a method call to a generated method, where that generated method returned the class object. Since it returned the class object, that means that the class was loaded and initialized at that point. So to be clear, if you compile with a pre-1.5 compiler (or with the -target set to a pre-1.5 version) then when the runtime hits a "XYZ.class" clause, you are guaranteed that the XYZ class is fully initialized before the expression holding the "XYZ.class" clause finishes being evaluated.
In Java 5 "XYZ.class" is a literal, stored in the class file. Doesn't sound like much of a change. But the difference is that in Java 5 hitting the "XYZ.class" clause at runtime does not force the class to be initialized. The Java 5 release notes detail this, saying that "Previously, evaluating a class literal (for example, Foo.class) caused the class to be initialized; as of 5.0, it does not. Code that depends on the previous behavior should be rewritten.". In the project we were fully aware of this, it was one of the changes that we identified right at the beginning when we "analysed the Java 5 new features". But at the time it didn't seem like there was any consequence for us. Unfortunately we found out after production deployment that what we had was a classic race condition - some classes depended on other classes being loaded before they were in turn loaded, and some of those classes used XYX.class in their class initialization code to check for things. And you all know what a race condition can lead to when you change the conditions - yes, a deadlock. We hit deadlocks at classloading time. In production. Bad practice number 5 was that we had relied on class loading order using "XYZ.class" to enforce that loading order.
So those were our Java 5 upgrade woes. We would have avoided them if we had followed best coding practices. Of course we wouldn't have hit any of these without Sun making some seemingly minor changes to Java 5, but can we blame Sun for us following bad practices? I don't really think so, that way leads to stagnation and there's no doubt in my mind that Sun engineering do make a big attempt to remain backwardly compatible wherever possible. Well that's the end of my tale. I hope this article helps you avoid some of these problems in your upgrade. Good luck.
--Hutt5
Back to newsletter 075 contents