How to disassemble Java code with ‘javap -c’

One of my favorite Java subjects is source code optimization and performance. Here I'd like to show you a couple of neat things you can learn with the javap -c command. This command lets you disassemble Java bytecode.

The first thing you need to have for this exercise is a little sample Java code. So in the examples below I create two test Java classes, appropriately named Test1.java and Test2.java. Although it's not explicitly stated below, the steps I'm going to follow are these:

  • Create the Test class file with my favorite vi editor.
  • Compile the Test class, like javac Test1.java
  • Disassemble the Test class, like javap -c Test1

So the first thing I do is create a test class named Test1.java. Here is the source code for that class:

public class Test1
{
  String s = null;

  public Test1()
  {
    s = new String();
  }
}

Next, after compiling the code with javac, I disassemble the resulting .class file with "javap -c". Here is the output I get when I run the command:

$ javap -c Test1
Compiled from Test1.java
public class Test1 extends java.lang.Object {
    java.lang.String s;
    public Test1();
}

Method Test1()
   0 aload_0
   1 invokespecial #1     4 aload_0    5 aconst_null    6 putfield #2     9 aload_0   10 new #3    13 dup   14 invokespecial #4    17 putfield #2    20 return 

The code I'm most interested in is the part that looks like assembly code at the bottom, after the line Method Test1(). Take a brief look at that section, then move on to the next step below.

Next we create Test2.java that has one slight change. Let's initialize the String s at the top of the program as well as in the constructor. With this change the code looks like this:

public class Test2
{
  String s = new String();

  public Test2()
  {
    s = new String();
  }
}

Next I run "javap -c" on Test2, and the output looks like this:

$ javap -c Test2
Compiled from Test2.java
public class Test2 extends java.lang.Object {
    java.lang.String s;
    public Test2();
}

Method Test2()
   0 aload_0
   1 invokespecial #1     4 aload_0    5 new #2     8 dup    9 invokespecial #3    12 putfield #4    15 aload_0   16 new #2    19 dup   20 invokespecial #3    23 putfield #4    26 return 

Note that the section under Method Test2() appears to be longer than the same section for the Test1 class. Doing a simple diff on the output of these two commands, I can see that they are most definitely different, and that the output from Test2 is much longer than the output of Test1:

11,18c11,20
<    5 aconst_null
<    6 putfield #2  <    9 aload_0 <   10 new #3  <   13 dup <   14 invokespecial #4  <   17 putfield #2  <   20 return --- >    5 new #2  >    8 dup >    9 invokespecial #3  >   12 putfield #4  >   15 aload_0 >   16 new #2  >   19 dup >   20 invokespecial #3  >   23 putfield #4  >   26 return 

Lessons Learned?

This is getting longer than I wanted to make it here, so let me state some important things, without getting into the detail of the disassembled code:

  1. We've gone from 11 steps in the assembly code to 13 steps. Whatever the JVM is going to do, it has at least two more steps it has to do.
  2. More importantly, it's obvious that the compiler is going to create String objects twice for class Test2.java. You can see this in the invokespecial and putfield lines of the disassembled code.
  3. Creating new String objects is not a "cheap" operation.
  4. Other than the poor coding of Test2, the Test1 and Test2 classes do exactly the same thing, from the perspective of a user of those classes.

Probably the one important lesson to take away from this brief writing is that you should get comfortable disassembling code with javap -c so you can understand the work that the JVM is going to do on your behalf. As time goes on, you will begin to see that certain ways of writing code, like the example above, really make your code less than optimal, and cause the JVM to perform more work than is necessary.