Core Java

javap Usage Unfolds: What’s Hidden Inside Your Java Class Files?

What’s javap, how do you use it and when would you want to disassemble class files?

There are a number of tools available to us as part of the Java Development Kit (JDK), that help gain a better understanding of Java code. One of those tools is the javap command, that gives us backstage passes to Java class files.

In the following post, we’ll take a look into javap, understand how it can help us and see exactly how we can use it. Code, disassemble!

What is javap?

javap is a command line tool that disassembles Java class files: it takes apart our class files, and reveals what’s inside. The tool translates a binary format class file to human readable code. Well, for some humans.

There are a number of outputs javap offers, which vary according to the data we’re interested in. By default, javap prints declarations of non-private members of each of the classes.

As for what the p in javap stands for, all evidence points to “prints”, since the javap command prints out the bytecode within the class.

A nice way in which we can use javap, is to explore exceptions. If you want to level up your knowledge and see what happens when an exception is thrown, check out our post about the surprising truth of Java exceptions.

Using javap with our classes

Now that we know what javap is, it’s time to experiment using it on our code. We do that by typing the command, choosing an option and adding the class name:

javap [options] classname

As we pointed out, the options, which we can also call flags, will determine what our output will be. We can choose from a number of flags, that include:

  • -l – Prints out line and local variable tables
  • -public – Shows only public classes and members
  • -protected – Shows only protected and public classes and members
  • -package – Shows only package, protected, and public classes and members
  • -p – Shows all classes and members
  • -Jflag – Pass flag directly to the runtime system
  • -s – Prints internal type signatures
  • -sysinfo – Shows system information (path, size, date, MD5 hash) of the class being processed
  • -constants – Shows static final constants
  • -c – Prints out disassembled code
  • -verbose – Prints stack size, number of locals and args for methods

Diving into the bytecode with javap

After we’ve listed what we can do with javap, it’s time to figure how it actually works. In order to do so, we’ve create a basic class called ExampleClass:

public class ExampleClass
{
    private int a = 0;
    private int b = 0;
    
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

Now let’s take a deeper look at it with the help of javap. First, we’ll use the javap command without any other flags, to print out non-private members. Our output is this:

$ javap ExampleClass

Compiled from "ExampleClass.java"
public class ExampleClass {
  public ExampleClass();
  public static void main(java.lang.String[]);
}

As you can see, this is a pretty “plain” view of our original code, without any information about our private integers and logic. That’s a good start, but what if we want to look even deeper? Let’s try using -c, to print out the disassembled code:

$ javap -c ExampleClass

Compiled from "ExampleClass.java"
public class ExampleClass {
  public ExampleClass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2                  // Field a:I
       9: aload_0
      10: iconst_0
      11: putfield      #3                  // Field b:I
      14: return
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #5                  // String Hello world!
       5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

Now we have our bytecode in a somewhat-readable form, where we can identify our methods, integers, commands and strings. But wait, there’s more. What if we want more information about this class? That’s where our verbose flag can help us out:

$ javap -v ExampleClass

Classfile /Users/es/ExampleClass.class
  Last modified May 22, 2017; size 496 bytes
  MD5 checksum 7d29362228a3128e67b0c20c8bb54ee1
  Compiled from "ExampleClass.java"
public class ExampleClass
  SourceFile: "ExampleClass.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#20         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #7.#21         //  ExampleClass.a:I
   #3 = Fieldref           #7.#22         //  ExampleClass.b:I
   #4 = Fieldref           #23.#24        //  java/lang/System.out:Ljava/io/PrintStream;
   #5 = String             #25            //  Hello world!
   #6 = Methodref          #26.#27        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   #7 = Class              #28            //  ExampleClass
   #8 = Class              #29            //  java/lang/Object
   #9 = Utf8               a
  #10 = Utf8               I
  #11 = Utf8               b
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               SourceFile
  #19 = Utf8               ExampleClass.java
  #20 = NameAndType        #12:#13        //  "<init>":()V
  #21 = NameAndType        #9:#10         //  a:I
  #22 = NameAndType        #11:#10        //  b:I
  #23 = Class              #30            //  java/lang/System
  #24 = NameAndType        #31:#32        //  out:Ljava/io/PrintStream;
  #25 = Utf8               Hello world!
  #26 = Class              #33            //  java/io/PrintStream
  #27 = NameAndType        #34:#35        //  println:(Ljava/lang/String;)V
  #28 = Utf8               ExampleClass
  #29 = Utf8               java/lang/Object
  #30 = Utf8               java/lang/System
  #31 = Utf8               out
  #32 = Utf8               Ljava/io/PrintStream;
  #33 = Utf8               java/io/PrintStream
  #34 = Utf8               println
  #35 = Utf8               (Ljava/lang/String;)V
{
  public ExampleClass();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_0
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: iconst_0
        11: putfield      #3                  // Field b:I
        14: return
      LineNumberTable:
        line 1: 0
        line 3: 4
        line 4: 9
  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String Hello world!
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
}

A whole lot of lines unravel before us. We can see the following things:

1. Metadata – Where this class file is located, when it was last modified, and the class hash with its unique id
2. Constant pool – a table of structures that refers to the various constants within the ClassFile structure and its substructures. It holds the symbolic information of classes, interfaces, class instances, field names, string constants or arrays.
3. Bytecode – an instruction set written in a language the JVM can “read”

We at OverOps use javap as a research tool, to dig inside classes. We’ve used it recently to discover how IBM J9 works, to offer support for developers who use this JVM. If you want to learn more about how we help teams cut down debugging time and know where, when and why code breaks in production – click here to schedule a demo.

Now that we know how we can use javap, it’s time to answer an important question:

What is it good for?

Javap is cool, especially if you’re a data fan like us and want to know what’s going on behind the code. But beside the nice code-exploration adventure, it’s also pretty useful.

It could come in handy for those cases in which the original source code isn’t available, showing what methods are available for your use. It could also help find out what’s inside someone else’s code, or a 3rd party class.

We can also use javap as a class verifier on Java classes, making sure each loaded class file is structured in the appropriate way.

At first, javap feels like a magic wand that gives us every piece of information within the code, but it’s not a complete solution to every problem we’re facing. It might help “reverse-engineer” some of the code, but it’s more of a clue in an elaborate puzzle.

If you’re looking for a way to turn javap bytecode output to functional Java code, and not just a “data-list”, you’d need the help of decompiling tools such as JD, Mocha, and others.

Final Thoughts

While you won’t use it often, javap is a useful tool to keep in mind. Other than the fact that it’s a cool “trick”, it offers a deeper dive into Java code, showing us what’s going on behind the scenes and how the JVM consumes our code.

Henn Idan

Henn works at OverOps, helping developers know when and why code breaks in production. She writes about Java, Scala and everything in between. Lover of gadgets, apps, technology and tea.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Evgeny
6 years ago

Very helpful article. Thank you, I found a lot of interesting things here )

Back to top button