The javap is a class file dissembler. Here are 3 scenarios you can put javap to use with code examples.
#1 Finding the Java version of the class file within a jar
Extract a class file say “MyJobRunner.class” from a jar file and use javap to find out the version used
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
bash-3.2$ javap -v MyJobRunner Compiled from "MyJobRunner.java" public interface com.myapp.invoke.MyJobRunner SourceFile: "MyJobRunner.java" RuntimeVisibleAnnotations: length = 0x6 00 01 00 08 00 00 minor version: 0 major version: 51 Constant pool: const #1 = class #9; // com/myapp/invoke/MyJobRunner const #2 = class #10; // java/lang/Object const #3 = Asciz executeIlliquidDecsProcess; const #4 = Asciz ()V; const #5 = Asciz SourceFile; const #6 = Asciz MyJobRunner.java; const #7 = Asciz RuntimeVisibleAnnotations; const #8 = Asciz Lorg/springframework/stereotype/Service;; const #9 = Asciz com/myapp/invoke/MyJobRunner; const #10 = Asciz java/lang/Object; { public abstract void executeIlliquidDecsProcess(); } |
“major version: 51” means Java 7.
1 2 3 4 5 6 |
J2SE 8 = 52, J2SE 7 = 51, J2SE 6.0 = 50, J2SE 5.0 = 49, JDK 1.4 = 48, |
So, very useful for debugging Java version issues throwing errors like
1 |
java.lang.UnsupportedClassVersionError: org/apache/cxf/management/annotation/ManagedResource : Unsupported major.minor version 51.0 |
#2 Finding or proving any inefficiencies in your code
Is there anything wrong with this code?
1 2 3 4 5 6 7 8 9 10 11 12 |
public class StringConcat { public static String withoutStringBuilder(int count) { String str = ""; for (int i = 0; i < count; i++) { str += i; } return str; } } |
javap to dissemble
1 |
javap -c StringConcat |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
Compiled from "StringConcat.java" public class StringConcat extends java.lang.Object{ public StringConcat(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static java.lang.String withoutStringBuilder(int); Code: 0: ldc #2; //String 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: iload_0 7: if_icmpge 35 10: new #3; //class java/lang/StringBuilder 13: dup 14: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V 17: aload_1 18: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: iload_2 22: invokevirtual #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 25: invokevirtual #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 28: astore_1 29: iinc 2, 1 32: goto 5 35: aload_1 36: areturn } |
Hmm, what does this mean?
The dissembled code looks cryptic, but if you inspect it carefully the code within the public static java.lang.String withoutStringBuilder(int);
Line 5 to 32: is the code within the for loop. The “goto 5” indicates looping back.
Line 10: creates a new StringBuilder object every time
Line 18: uses the StringBuilder’s append method to concatenate the String.
Line 25: uses the toString( ) method to convert the StringBuilder back to the existing String reference via toString( ) method.
Inefficient code why?
if the count = 100, then 100 StringBuilder objects are created due to line 10, and 100 String objects out of which 99 will discarded due to line 25.
The efficient code would be
1 2 3 4 5 6 7 8 9 10 11 12 |
public class StringConcat { public static String withStringBuilder(int count) { StringBuilder sb = new StringBuilder(100); for (int i = 0; i < count; i++) { sb.append(i); } return sb.toString(); } } |
javap to dissemble
1 |
javap -c StringConcat |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
Compiled from "StringConcat.java" public class StringConcat extends java.lang.Object{ public StringConcat(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static java.lang.String withStringBuilder(int); Code: 0: new #2; //class java/lang/StringBuilder 3: dup 4: bipush 100 6: invokespecial #3; //Method java/lang/StringBuilder."<init>":(I)V 9: astore_1 10: iconst_0 11: istore_2 12: iload_2 13: iload_0 14: if_icmpge 29 17: aload_1 18: iload_2 19: invokevirtual #4; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 22: pop 23: iinc 2, 1 26: goto 12 29: aload_1 30: invokevirtual #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 33: areturn } |
Why is it efficient?
As you could see
Line 0 to 6: initializes one StringBuilder object outside the for loop.
Line 12 to 26: is the for loop.
Line 19: indicates that since the StringBuilder is mutable, the string is appended via the append method.
So, it does not create unnecessary number of StringBuilder and String objects.
#3 Improves your understanding of Generics type erasure
Here is the sample code with Generics
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.debug.multithread; public class MyGeneric <K, V> { K key; V value; public MyGeneric(K key, V value) { this.key = key; this.value = value; } } |
1 |
javap -c MyGeneric.class |
In order to see the resulting code of “MyGeneric” does
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Compiled from "MyGeneric.java" public class com.debug.multithread.MyGeneric<K, V> { K key; V value; public com.debug.multithread.MyGeneric(K, V); Code: 0: aload_0 1: invokespecial #15 // Method java/lang/Object."<init>":()V 4: aload_0 5: aload_1 6: putfield #18 // Field key:Ljava/lang/Object; 9: aload_0 10: aload_2 11: putfield #20 // Field value:Ljava/lang/Object; 14: return } |