Sunday, December 7, 2008

A case against static initializers

"Effective Java" is an excellent book. I recently bought the 2nd edition, and it is absolutely fabulous, priceless. However after quite some time in the industry, I've learnt not to take any advice blindly. First edition was also excellent, but several of the items were revisited since then. So here's an item that I have mixed feelings about - Avoid creating unnecessary objects. The advice is to use static initializers for the expensive computation. 

Static initializers are double-edged sword. It's like with the stock exchange in times of crisis - for a particular individual it may be a good idea to sell the stock, but the trouble is that everybody's doing it, and in the end everybody's losing big-time. Same applies to static initializers. One or two may seem harmless, but they add up and together create a big problem. The fact that static initializers are (edit) may be invoked at program start-up affects everybody and since they potentially interfere with classloading, it's very hard (edit) harder to debug them if anything goes wrong.

Here is how it usually gets out of hand: people start with initializing static members in static blocks. Map of values, sort of configuration details. That alone sounds harmless. But soon comes the time when the values in a map need to be read from a properties file, so here we got IO within static block. Uh oh, better catch these exceptions. Before you notice the whole thing turns into a puzzler. Don't believe me? Here's a real problem I had.

Server in Java, client is either applet or JNLP. On certain machines, only one of them can run. You run server first - client never comes up, no error whatsoever. You reboot and connect as client to another machine - no problem. But if you try to start up the server locally - silent death. A team in India spends months on it. In vain. The whole release is detained, escalation to senior management. The thing ends up on my desk after a ruthless blame game between teams. Long story short: it's a DirectX problem. Why the **** does the server need DirectX? Ah. What's next after reading defaults from a properties file? Reading them from the database. Oh, but it's a different process, and the database needs to be up. So we not just connect to database from initialization block, we wait for the database process to be up. Great idea. How? We follow "best coding practices" and reuse: find a poller utility somewhere in the JDK. Apparently there is a java.awt.Timer. Why not? Great idea. Apparently, a touch of one AWT class causes a bunch of other AWT classes to load, which in turn loads DirectX and OpenGL dlls. And guess what - Windows on some machines has a nasty bug, that only allows to load them once per machine, regardless of the user. And when another user tries to do it - the loading gets stuck. And our server is of course a system process, while the client belongs to the logged in user. 

Since it was a last minute fix, we solved it with some JVM flags that disabled DirectX and OpenGL. The problem was not the fix, but the diagnosis. If it was part of the regular code, it would have been easy to connect with a debugger, see what call gets stuck, investigate it from there. But as it was part of start-up, people didn't know where to look. Not to mention the man-months accumulatively spent by developers who waited for the server to restart when testing. 

So... what's the lesson here? Life is better without static, avoid it as much as you can. 

9 comments:

konberg said...

Static Initializers are not run at program startup, they're run on a class-by-class basis as required.
Your problem sounds awful, but I don't understand how static failed to have anything to do with this. Static initializers can _always_ be stopped with breakpoints.

Yardena said...

Robert - you are right, static initializers run when the class is loaded, not necessarily, but possibly at program start.

I am not claiming it's impossible to debug such problem, but the fact is that it is harder. In our case the machine was remote, JDK was old, but even if we could stop on breakpoint, nobody knew where to put one... The way I found the root cause was by running JDK examples on the same machine while the server was up; when a Swing example got stuck I took a thread dump and ran it through Sun bug database; then I searched server code for AWT use.

Once you put non-trivial code in initializer, you may cause a chain of other classes to load, different threads may become involved, and since JVM has special locking mechanisms for class-loading, you may find yourself in a complex situation. We already know that these mechanisms can be used for our benefit, such as in lazy singleton with a Holder class, but we also need to be cautious. Another aspect is that we tend to say "I'll just load this at start-up, we're gonna use it anyway" but then anybody who tests part of the system pays the price of slow start-up, possibly for no reason. Our server took 5-10 minutes to start up, and I know it's not uncommon. I'm not saying it's all static blocks fault, but I would speculate that code written with the mindset of pre-initialization is one of the contributors.

Yardena said...

Of course, the programming environment could load from an image, safely and blazingly fast... wait, this post is supposed to be about Java :-)

Paul Beckford said...

Hi Yardena,

A few observations. We've suffered horrible bugs to do with server software wanting to load UI stuff and have had to resort to running our JVM in an "headless" mode too. I never cared enough to get to the bottom of it, but like with you it took a long time to resolve.


Sounds like you've found an anti-pattern here with static initialisers.

I like lazy initialisation when you create objects as you need them. Like you say this helps out with startup time and just feels less wasteful.

At one stage object creation was expensive in java, and perhaps thats where this advice comes from, but I believe this is no longer the case, but the practice of early object creation (connection pools etc) still persists.

Yes an Image would solve the problem nicely :) but like so much with Smalltalk it runs against the grain. An image gets rid of the need for an OS too, but again Operating Systems have become so entrenched that people just don't want to give them up either :)

I sense an hardening in your attitude. Is it to do with you learning Smalltalk? It had the same affect on me. So blindingly better yet the world doesn't see it :(

I like Gilads approach of bringing the mountain to Mohamed and bridging the gap between the familiar and the unfamiliar. His Alien objects should help Newspeak integrate into the current technology ecosystem and in so doing gain acceptance in the mainstream.

We'll have to wait and see :)

Paul.

Yardena said...

Hi Paul, yes, Smalltalk must be affecting me in many ways :-) But actually I discouraged people from writing "non-trivial" static initializers long before I heard about Smalltalk. Maybe because all the nastiest puzzler bugs in our project, especially class-loading ones, ended up in my task-list. After you see this stuff, you become more cautious...

Unknown said...

It also would have been better to use the java.util.Timer rather than the java.awt.Timer (which fires on the EDT).

Yardena said...

jkinzer - yes, absolutely. A series of bad decisions led to this.

Anonymous said...

This has nothing to do with your actual post.. but "crysis"? The word you're looking for is 'crisis' ;-)

Unknown said...

It seems to me that this post is about people not knowing how to debug the JVM rather than about static initializers. If a team of developers (two teams in this case) are not able to trace the class loader and see where it hangs - the fire the teams don't blame the language!

I have hit issues with class initializers before. The solution is to run:

java -verbose

and see where the class loader is when the problem its. Form there you can inspect the code it is trying to load. The source of the jdk is available for heaven's sake!

Anyhow - sounds like you know what you are doing - so good on you.