Javap Tool for Disassembling Classes in Java

Introduction to Javap

javap is the Java Class File Disassembler included with the JDK. It disassembles compiled Java class files and shows the structure, bytecode, and metadata of classes in a human-readable format.

What Javap Shows

  • Class declaration and inheritance
  • Fields and methods with signatures
  • Bytecode instructions
  • Constant pool information
  • Access modifiers and annotations

1. Basic Javap Usage

Command Line Syntax

# Basic disassembly
javap className
# With full verbose output
javap -v className
# Disassemble with specific options
javap -c -p -s className

Simple Example Class

// Simple class for disassembly demonstration
public class SimpleCalculator {
private int value;
public SimpleCalculator() {
this.value = 0;
}
public SimpleCalculator(int initialValue) {
this.value = initialValue;
}
public int add(int number) {
return this.value + number;
}
public static int multiply(int a, int b) {
return a * b;
}
private void reset() {
this.value = 0;
}
}

Basic Disassembly Output

# Compile first
javac SimpleCalculator.java
# Basic disassembly
javap SimpleCalculator

Output:

Compiled from "SimpleCalculator.java"
public class SimpleCalculator {
private int value;
public SimpleCalculator();
public SimpleCalculator(int);
public int add(int);
public static int multiply(int, int);
private void reset();
}

2. Common Javap Options

Complete Option Reference

OptionDescription
-helpPrint help message
-versionVersion information
-v or -verboseVerbose output including bytecode
-lPrint line and local variable tables
-publicShow only public classes and members
-protectedShow protected/public classes and members
-packageShow package/protected/public members (default)
-p or -privateShow all classes and members
-cDisassemble bytecode
-sPrint internal type signatures
-sysinfoShow system info (path, size, date, MD5 hash)
-constantsShow static final constants

3. Detailed Bytecode Analysis

Verbose Disassembly Example

javap -v -p SimpleCalculator

Partial Output:

Classfile /path/to/SimpleCalculator.class
Last modified Dec 1, 2024; size 487 bytes
MD5 checksum a1b2c3d4e5f6...
Compiled from "SimpleCalculator.java"
public class SimpleCalculator
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5                          // SimpleCalculator
super_class: #6                         // java/lang/Object
interfaces: 0, fields: 1, methods: 4, attributes: 1
// Constant Pool (abbreviated)
Constant pool:
#1 = Methodref          #6.#21         // java/lang/Object."<init>":()V
#2 = Fieldref           #5.#22         // SimpleCalculator.value:I
#3 = Methodref          #5.#23         // SimpleCalculator."<init>":(I)V
// ... more constants
// Fields
private int value;
descriptor: I
flags: (0x0002) ACC_PRIVATE
// Constructors and Methods
public SimpleCalculator();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, 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 value:I
9: return
public int add(int);
descriptor: (I)I
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: getfield      #2                  // Field value:I
4: iload_1
5: iadd
6: ireturn

Bytecode Instruction Analysis

public class BytecodeExample {
private String name;
private int count;
public BytecodeExample(String name) {
this.name = name;
this.count = 0;
}
public void increment() {
count++;
}
public String process(int multiplier) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < multiplier; i++) {
sb.append(name).append("-").append(count);
}
return sb.toString();
}
}

Disassemble with bytecode:

javap -c -p BytecodeExample

Output:

public class BytecodeExample {
private java.lang.String name;
private int count;
public BytecodeExample(java.lang.String);
Code:
0: aload_0
1: invokespecial #1  // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield      #2  // Field name:Ljava/lang/String;
9: aload_0
10: iconst_0
11: putfield      #3  // Field count:I
14: return
public void increment();
Code:
0: aload_0
1: dup
2: getfield      #3  // Field count:I
5: iconst_1
6: iadd
7: putfield      #3  // Field count:I
10: return
public java.lang.String process(int);
Code:
0: new           #4  // class java/lang/StringBuilder
3: dup
4: invokespecial #5  // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: iload_1
12: if_icmpge     49
15: aload_2
16: aload_0
17: getfield      #2  // Field name:Ljava/lang/String;
20: invokevirtual #6  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc           #7  // String -
25: invokevirtual #6  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: aload_0
29: getfield      #3  // Field count:I
32: invokevirtual #8  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
35: pop
36: iinc          3, 1
39: goto          10
42: astore        4
44: aload_2
45: invokevirtual #9  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
48: areturn
49: aload_2
50: invokevirtual #9  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
53: areturn
}

4. Advanced Class Analysis

Analyzing Inheritance and Interfaces

// Complex class with inheritance and interfaces
public interface Shape {
double area();
double perimeter();
}
public abstract class AbstractShape implements Shape {
protected String color;
public AbstractShape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
public class Circle extends AbstractShape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
public final double getRadius() {
return radius;
}
}

Analyze the Circle class:

javap -v -p Circle

Key Output Sections:

// Class hierarchy
public class Circle extends AbstractShape
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
// Interfaces implemented
Interfaces:
#0 = Shape
// Method overrides and implementations
public double area();
descriptor: ()D
flags: (0x0001) ACC_PUBLIC
Code:
stack=6, locals=1, args_size=1
0: ldc2_w        #2    // double 3.141592653589793d
3: dload_0
4: getfield      #4    // Field radius:D
7: dmul
8: dload_0
9: getfield      #4    // Field radius:D
12: dmul
13: dreturn
public final double getRadius();
descriptor: ()D
flags: (0x0011) ACC_PUBLIC, ACC_FINAL

Analyzing Annotations

import java.lang.annotation.*;
import javax.validation.constraints.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Author {
String name();
String date();
}
@Author(name = "John Doe", date = "2024-12-01")
public class AnnotatedClass {
@NotNull
private String title;
@Min(1) @Max(100)
private int priority;
@Deprecated
public void oldMethod() {}
public void newMethod(@NotNull String param) {}
}

Analyze annotations:

javap -v AnnotatedClass

Annotations Section:

// Class annotations
RuntimeVisibleAnnotations:
0: #30(#31=s#32,#33=s#34)
Author(
name="John Doe"
date="2024-12-01"
)
// Field annotations
private java.lang.String title;
descriptor: Ljava/lang/String;
flags: (0x0002) ACC_PRIVATE
RuntimeVisibleAnnotations:
0: #35()
javax.validation.constraints.NotNull
// Method annotations
public void oldMethod();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Deprecated: true
public void newMethod(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: (0x0001) ACC_PUBLIC
RuntimeVisibleParameterAnnotations:
parameter 0:
0: #35()
javax.validation.constraints.NotNull

5. Practical Use Cases and Examples

Debugging and Understanding Code

public class DebugExample {
private static final int MAX_SIZE = 100;
private int[] data;
private int size;
public DebugExample() {
this.data = new int[MAX_SIZE];
this.size = 0;
}
public void add(int value) {
if (size >= MAX_SIZE) {
throw new IllegalStateException("Array full");
}
data[size++] = value;
}
public int find(int value) {
for (int i = 0; i < size; i++) {
if (data[i] == value) {
return i;
}
}
return -1;
}
public void process() {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += data[i] * 2;
}
System.out.println("Sum: " + sum);
}
}

Analyze for optimization:

javap -c -v DebugExample

Understanding Compiler Optimizations

public class OptimizationExample {
// Analyze how compiler handles different constructs
public int simpleLoop() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
public int stringConcatenation() {
String result = "";
for (int i = 0; i < 10; i++) {
result += i;  // Inefficient concatenation
}
return result.length();
}
public int optimizedString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
return sb.toString().length();
}
public final int constantFolding() {
return 10 + 20 * 30;  // Should be compiled to constant
}
}

Compare bytecode:

# Compare string concatenation vs StringBuilder
javap -c OptimizationExample

Bytecode Comparison:

// Inefficient string concatenation
public int stringConcatenation();
Code:
0: ldc           #2  // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: bipush        10
8: if_icmpge     37
11: new           #3  // class java/lang/StringBuilder
14: dup
15: invokespecial #4  // Method java/lang/StringBuilder."<init>":()V
18: aload_1
19: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_2
23: invokevirtual #6  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #7  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_1
30: iinc          2, 1
33: goto          5
36: return
// Efficient StringBuilder usage
public int optimizedString();
Code:
0: new           #3  // class java/lang/StringBuilder
3: dup
4: invokespecial #4  // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: bipush        10
13: if_icmpge     30
16: aload_1
17: iload_2
18: invokevirtual #6  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: pop
22: iinc          2, 1
25: goto          10
28: return

6. Advanced Javap Techniques

Analyzing Inner Classes

public class OuterClass {
private String outerField = "outer";
public class InnerClass {
private String innerField = "inner";
public void print() {
System.out.println(outerField + " - " + innerField);
}
}
public static class StaticNestedClass {
private String staticField = "static";
public void print() {
System.out.println(staticField);
}
}
public void createLocalClass() {
class LocalClass {
public void print() {
System.out.println("Local class");
}
}
new LocalClass().print();
}
public Runnable createAnonymousClass() {
return new Runnable() {
@Override
public void run() {
System.out.println("Anonymous class");
}
};
}
}

Analyze compiled inner classes:

# Compile first
javac OuterClass.java
# Analyze main class
javap -v OuterClass
# Analyze inner classes
javap -v OuterClass\$InnerClass
javap -v OuterClass\$StaticNestedClass
javap -v OuterClass\$1LocalClass
javap -v OuterClass\$1

Inner Class Characteristics:

// Inner class has reference to outer class
public class OuterClass$InnerClass
flags: (0x0020) ACC_SUPER
InnerClasses:
#24= #2 of #23; // InnerClass=class OuterClass$InnerClass of class OuterClass
// Access to outer class field
aload_0
getfield      #1  // Field this$0:LOuterClass;
getfield      #2  // Field OuterClass.outerField:Ljava/lang/String;

Analyzing Synthetic Methods

public class SyntheticExample {
private String privateField;
public class Inner {
public void accessPrivate() {
// Accessing private field of outer class
// generates synthetic accessor method
System.out.println(privateField);
}
}
}

Synthetic methods in output:

// Synthetic accessor method generated by compiler
static java.lang.String access$000(SyntheticExample);
descriptor: (LSyntheticExample;)Ljava/lang/String;
flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield      #1  // Field privateField:Ljava/lang/String;
4: areturn

7. Javap with Java Program

Programmatic Javap Execution

import java.io.*;
import java.util.*;
public class ProgrammaticJavap {
/**
* Execute javap command programmatically and capture output
*/
public static List<String> disassembleClass(String className, String... options) 
throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
command.add("javap");
// Add options
if (options != null) {
command.addAll(Arrays.asList(options));
}
// Add class name
command.add(className);
ProcessBuilder pb = new ProcessBuilder(command);
pb.redirectErrorStream(true);
Process process = pb.start();
// Read output
List<String> output = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.add(line);
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("javap failed with exit code: " + exitCode);
}
return output;
}
/**
* Analyze multiple classes with different options
*/
public static void analyzeClasses(String... classNames) {
for (String className : classNames) {
try {
System.out.println("\n=== Analyzing: " + className + " ===");
// Basic analysis
List<String> basicOutput = disassembleClass(className);
System.out.println("Basic structure:");
basicOutput.forEach(System.out::println);
// Bytecode analysis
System.out.println("\nBytecode:");
List<String> bytecodeOutput = disassembleClass(className, "-c");
bytecodeOutput.forEach(System.out::println);
} catch (Exception e) {
System.err.println("Failed to analyze " + className + ": " + e.getMessage());
}
}
}
public static void main(String[] args) {
// Example usage
try {
// Analyze current class
analyzeClasses("ProgrammaticJavap");
// Analyze system classes
List<String> stringOutput = disassembleClass("java.lang.String", "-c", "-p");
System.out.println("\n=== String class bytecode (first 20 lines) ===");
stringOutput.stream().limit(20).forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Custom Bytecode Analyzer

import java.util.regex.*;
import java.util.*;
public class BytecodeAnalyzer {
/**
* Parse javap output to extract method information
*/
public static Map<String, MethodInfo> analyzeMethods(List<String> javapOutput) {
Map<String, MethodInfo> methods = new LinkedHashMap<>();
Pattern methodPattern = Pattern.compile(
"^(public|private|protected|).*? (\\w+)\\(.*\\)");
Pattern bytecodePattern = Pattern.compile("^\\s+\\d+:\\s+(\\w+)");
String currentMethod = null;
List<String> currentBytecode = new ArrayList<>();
for (String line : javapOutput) {
Matcher methodMatcher = methodPattern.matcher(line);
if (methodMatcher.find()) {
// Save previous method
if (currentMethod != null) {
methods.put(currentMethod, new MethodInfo(currentMethod, currentBytecode));
}
// Start new method
currentMethod = methodMatcher.group(2);
currentBytecode = new ArrayList<>();
}
Matcher bytecodeMatcher = bytecodePattern.matcher(line);
if (bytecodeMatcher.find() && currentMethod != null) {
currentBytecode.add(bytecodeMatcher.group(1));
}
}
// Don't forget the last method
if (currentMethod != null) {
methods.put(currentMethod, new MethodInfo(currentMethod, currentBytecode));
}
return methods;
}
public static class MethodInfo {
private final String name;
private final List<String> instructions;
private final Map<String, Integer> instructionCount;
public MethodInfo(String name, List<String> instructions) {
this.name = name;
this.instructions = instructions;
this.instructionCount = new HashMap<>();
// Count instruction frequency
for (String instruction : instructions) {
instructionCount.merge(instruction, 1, Integer::sum);
}
}
// Getters
public String getName() { return name; }
public List<String> getInstructions() { return instructions; }
public Map<String, Integer> getInstructionCount() { return instructionCount; }
public int getInstructionCount() { return instructions.size(); }
}
public static void printAnalysis(Map<String, MethodInfo> methods) {
System.out.println("=== BYTECODE ANALYSIS ===");
System.out.printf("Total methods analyzed: %d%n%n", methods.size());
for (MethodInfo method : methods.values()) {
System.out.printf("Method: %s%n", method.getName());
System.out.printf("  Total instructions: %d%n", method.getInstructionCount());
System.out.printf("  Unique instructions: %d%n", 
method.getInstructionCount().size());
// Show most common instructions
System.out.println("  Most frequent instructions:");
method.getInstructionCount().entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.limit(5)
.forEach(entry -> 
System.out.printf("    %s: %d%n", entry.getKey(), entry.getValue()));
System.out.println();
}
}
}

8. Practical Tips and Best Practices

Common Javap Patterns

# 1. Quick method signature check
javap -p MyClass
# 2. Full bytecode analysis for debugging
javap -c -v MyClass > bytecode.txt
# 3. Compare two versions of a class
javap -c OldVersion.class > old.txt
javap -c NewVersion.class > new.txt
diff old.txt new.txt
# 4. Analyze system classes
javap -c java.lang.String
# 5. Check for synthetic methods and bridges
javap -v MyClass | grep -E "(synthetic|bridge)"
# 6. Analyze annotation processing
javap -v AnnotatedClass | grep -A5 -B5 "Annotation"

Understanding Common Bytecode Patterns

public class BytecodePatterns {
// Field access pattern
private int field;
public int getField() {
return field;  // aload_0, getfield, ireturn
}
// Method invocation pattern
public void methodCall() {
System.out.println("Hello");  // getstatic, ldc, invokevirtual
}
// Loop pattern
public void loop() {
for (int i = 0; i < 10; i++) {
// iconst_0, istore_1, iload_1, bipush 10, if_icmpge
// iinc 1, 1, goto
}
}
// Exception handling pattern
public void exceptionHandling() {
try {
riskyOperation();
} catch (Exception e) {
handleException(e);
}
}
// String concatenation pattern (Java 9+)
public String stringConcat(String a, String b) {
return a + b;  // invokedynamic #0, makeConcatWithConstants
}
}

Summary

Key Benefits of Javap:

  1. Debugging: Understand how Java code compiles to bytecode
  2. Performance Analysis: Identify inefficient bytecode patterns
  3. Learning: Learn Java internals and JVM operation
  4. Verification: Check compiler optimizations and synthetic methods
  5. Reverse Engineering: Understand third-party library behavior

Common Use Cases:

  • Performance Optimization: Identify bytecode-level inefficiencies
  • Compiler Behavior: Understand how language features compile
  • Educational Purposes: Learn JVM internals
  • Debugging: Troubleshoot mysterious behavior
  • Security Analysis: Examine potentially malicious code

Limitations:

  • Shows bytecode, not original source
  • Requires understanding of JVM instruction set
  • Verbose output can be overwhelming
  • Doesn't show runtime behavior

Javap is an essential tool for Java developers who want to understand what's happening under the hood and optimize their code at the bytecode level.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper