Software development needs more than just coding. There are a lot of things to do to move software functionality from running on developers’ laptop to going live on production. Developers need more skills other than coding to make their life less miserable through the whole software life cycle. One of the critical skills is about troubleshooting. This post I will talk about some little tool in JDK that has been helping me saving a lot of problem investigation time.
These tools are at the same directory with the “java” command in JDK installation (JRE installation doesn’t have them). I will simulate a story showing how the tools cold be used to make it more interesting and easier to related to.
Class file version mismatch
Let’s say you are about to test a program you get from your colleague as jar files. You execute it and get an exception.
The exception indicates that you are executing the program using JDK with version lower than the version of JDK used to compile the classes in this jar. If you are sure that the JDK you are using is of the right version according to product document then your colleague must be the one that misunderstand something. But before you go and accuse anybody of messing up build system, you may feel like you need more obvious evidence to support your claim. You can use javap command in JDK to disassemble a class in a jar file to see the actual class file version.
Below is the table to map major version to JDK version.
50 = JDK 6
49 = JDK 5
48 = JDK 4
In this case, the program was compiled using JDK 6 but you are running it on JDK 5 so the exception saying “Bad version number in .class file” is thrown
I have done this check quite several times in the past. The most recent one was when we were modifying our build system to build one code base for two target environments. One was for deploying on WAS 6.1 which uses Java 5 another was for WAS 8 which uses Java 6. The build script started the build for WAS 8 first and forgot to clean up all class files before starting the build for WAS6.1.
Back to our story, you notify your team about the problem and it gets fixed. Now you have two new jars but when you run it again, you get another exception.
Again, the error itself is quite enough to prove that something is missing from one of the jar files. But you have already raised a defect about the build system so you may feel like you want to double check this a bit. In Java, jar file is practically just a zip file. You can just unpack it to directory using command “jar -xf authen.jar” and look for auth/Connector.class file in the directory. Or you can just print out the content in jar file without unpacking it using command “jar –tf authen.jar”.
You can see that there is no Connector class in the jar file so you can be sure now that it is another defect in your build system.
I use this command a lot (Java is quite notorious for making things difficult with class path problem). Some library started with one jar then evolved into multiple jars e.g. one for API interface and another for implementation classes. I use this command to check whether a particular class is still in the jar I am including in my class path.
Now, let’s say you raise a new defect, wait for the fix and then get two new jars. When you run the test again, you got yet another exception.
$ java -classpath superb.jar;authen.jar superb.Main
Exception in thread “main” java.lang.NoSuchMethodError: auth.AuthenService.subjectBasedAuthen(Ljava/lang/String;)V
The method subjectBasedAuthen is missing from AuthenService class. May be superb.jar was compiled with the new version of authen.jar but this authen.jar you get here is the old version of the library. To make sure of this, you may use javap command again to see all the method available in the AuthenService class of your current authen.jar.
The command will show package,protected and public members of the class. You can see that there is no subjectBasedAuthen here so this is once again a problem related to your building system.
Your friends has again helped fix the problem and promised that it should work fine this time. The program does start without exception but when you fire a request to the program you just don’t get any response back. You wait a minute but the request still not came back. You start wondering what is exactly going on inside the program. If you can see the stack traces of threads inside your program then you may have some clue of what is going on in there.
First, you must use “jps” command to show process ids of all java process running on your machine. The output shows that the Main program we just run has id 5780. Now use “jstack” command to print out the stack traces of the program.
“pool-1-thread-3” prio=6 tid=0x02b25800 nid=0x6e4 runnable [0x02ecf000..0x02ecfa94] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:129) at java.io.BufferedInputStream.fill(BufferedInputStream.java:218) at java.io.BufferedInputStream.read1(BufferedInputStream.java:258) at java.io.BufferedInputStream.read(BufferedInputStream.java:317) – locked <0x22a0f2d0> (a java.io.BufferedInputStream) at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:687) at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:632) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1064) – locked <0x229f2a60> (a sun.net.www.protocol.http.HttpURLConnection) at auth.AuthenService.authenByUserId(AuthenService.java:15) at superb.Main$1.call(Main.java:25) at superb.Main$1.call(Main.java:1) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:619)
From the stack traces, it seems like our authenByUserId method use open http connection (using HttpUrlConnection) to somewhere. The request thread executes HttpURLConnection.getInputStream() method and never returns. It looks a lot like the server at another end of this connection gets stuck on something leaving client side wait for response indefinitely. Now you have a clue that the problem may be on the server side, not in your program.
Another nice thing of jstack command is that it also report thread deadlock. It is very helpful when you can see which threads participate in the deadlock on which object. The code review for solving deadlock will be a lot easier.
And that are all little tools in JDK that has been very helpful to me for long time. They didn’t do much but save me quite a great amount of troubleshooting time.