"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.
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.
9 comments:
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.
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.
Of course, the programming environment could load from an image, safely and blazingly fast... wait, this post is supposed to be about Java :-)
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.
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...
It also would have been better to use the java.util.Timer rather than the java.awt.Timer (which fires on the EDT).
jkinzer - yes, absolutely. A series of bad decisions led to this.
This has nothing to do with your actual post.. but "crysis"? The word you're looking for is 'crisis' ;-)
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.
Post a Comment