注意: javap 查看【class文件的字节码】信息                                                                                                                       

Java字节码深入解析

一:Java字节代码的组织形式

  类文件{

  OxCAFEBABE,小版本号,大版本号,常量池大小,常量池数组,访问控制标记,当前类信息,父类信息,实现的接口个数,实现的接口信息数组,域个数,域信息数组,方法个数,方法信息数组,属性个数,属性信息数组

  }

  二:查看方法 --- javap命令

  例子:有一个Java类Demo.java

/**
 * @author honglei
 * @since 2019-08-11
 */
public class Demo {
    /**
     * str1
     */
    private String str1;
    /**
     * str2
     */
    private String str2;
    /**
     * num1
     */
    private int num1;
    /**
     * num2
     */
    private int num2;
    /**
     * str3
     */
    public static final String STATIC_DATA = "hello world";

    /**
     * str1
     */
    private void sayHello1() {
        System.out.println("this is method1...");
    }

    /**
     * sayHello2
     */
    private void sayHello2() {
        System.out.println("this is method2...");
    }

    /**
     * sayHello3
     */
    private void sayHello3() {
        System.out.println("this is method3...");
    }
}

  通过jdk自带的反编译工具命令 javap 可以查看class文件的字节码信息

D:\>javap -verbose Demo >> Demo.txt

  Demo.txt:

sai:springbootdemo ws$ javap -verbose Demo.class 
Classfile /Users/ws/dev/SourceTree/springboot/springboot-demo/target/classes/com/example/springbootdemo/Demo.class
  Last modified Aug 11, 2019; size 1055 bytes
  MD5 checksum 98bea2d49bf0c5386ac21e3cae91d404
  Compiled from "Demo.java"
public class com.example.springbootdemo.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#36         // java/lang/Object."<init>":()V
   #2 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #39            // this is method1...
   #4 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #42            // this is method2...
   #6 = String             #43            // this is method3...
   #7 = String             #44            // test
   #8 = Class              #45            // com/example/springbootdemo/Demo
   #9 = Class              #46            // java/lang/Object
  #10 = Utf8               str1
  #11 = Utf8               Ljava/lang/String;
  #12 = Utf8               str2
  #13 = Utf8               num1
  #14 = Utf8               I
  #15 = Utf8               num2
  #16 = Utf8               STATIC_DATA
  #17 = Utf8               ConstantValue
  #18 = String             #47            // hello world
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               LocalVariableTable
  #24 = Utf8               this
  #25 = Utf8               Lcom/example/springbootdemo/Demo;
  #26 = Utf8               sayHello1
  #27 = Utf8               sayHello2
  #28 = Utf8               sayHello3
  #29 = Utf8               main
  #30 = Utf8               ([Ljava/lang/String;)V
  #31 = Utf8               args
  #32 = Utf8               [Ljava/lang/String;
  #33 = Utf8               MethodParameters
  #34 = Utf8               SourceFile
  #35 = Utf8               Demo.java
  #36 = NameAndType        #19:#20        // "<init>":()V
  #37 = Class              #48            // java/lang/System
  #38 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
  #39 = Utf8               this is method1...
  #40 = Class              #51            // java/io/PrintStream
  #41 = NameAndType        #52:#53        // println:(Ljava/lang/String;)V
  #42 = Utf8               this is method2...
  #43 = Utf8               this is method3...
  #44 = Utf8               test
  #45 = Utf8               com/example/springbootdemo/Demo
  #46 = Utf8               java/lang/Object
  #47 = Utf8               hello world
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               java/io/PrintStream
  #52 = Utf8               println
  #53 = Utf8               (Ljava/lang/String;)V
{
  public static final java.lang.String STATIC_DATA;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String hello world

  public com.example.springbootdemo.Demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/springbootdemo/Demo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String test
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 54: 0
        line 55: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Demo.java"

解析:

  1、版本号 major version: 49 //java版本 jdk1.6显示的是50, jdk1.5显示的是49,jdk1.4显示的是58 , 高版本能执行低版本的class文件

  2、常量池Constant pool

  Method:方法

  Field:字段

  String:字符串

  Asciz:签名如<init>由jvm调用,其他是不能够去调用它的

  NameAndType:变量名的类型

  Class:类

  通过字节码,我们可以看到Demo类 继承于java.lang.Object,如果类中没有显式声明构造函数的话,编译器会插入一个缺省无参的构造函数(构造函数在JVM级别是显示成<init>的普通函数)。

  三:检测代码的效率问题

  学习Java的过程中,都会了解到字符串合并时要用到StringBuffer 来代替String,那下面就来通过Java字节码来验证两种方式的效率性。

  例子:一个Java类 TestString.java

  • <strong>public class TestString { 
  •     public String testString(String str1, String str2){ 
  •        return str1 + str2; 
  •     } 
  •     public String testStringBuffer(StringBuffer sb, String str){ 
  •        return sb.append(str).toString(); 
  •     } 
  •  </strong>

  javap –c TestString 后字节码信息:

  • Compiled from "TestString.java" 
  • public class TestString extends java.lang.Object{ 
  • public TestString(); 
  •   Code: 
  •    0:      aload_0 
  •    1:      invokespecial  #8; //Method java/lang/Object."<init>":()V 
  •    4:      return 
  •   
  • public java.lang.String testString(java.lang.String, java.lang.String); 
  •   Code: 
  •    0:      new #16; //class java/lang/StringBuilder 
  •    3:      dup 
  •    4:      aload_1 
  •    5:      invokestatic    #18; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 
  •    8:      invokespecial  #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 
  •    11:     aload_2 
  •    12:    invokevirtual  #27; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
  •    15:    invokevirtual  #31; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
  •    18:    areturn 
  •   
  • public java.lang.String testStringBuffer(java.lang.StringBuffer, java.lang.String); 
  •   Code: 
  •    0:      aload_1 
  •    1:      aload_2 
  •    2:      invokevirtual  #40; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 
  •    5:      invokevirtual  #45; //Method java/lang/StringBuffer.toString:()Ljava/lang/String; 
  •    8:      areturn 
  • }

  从上面编译后的字节码信息可以看出来,方法testString 调用了五个方法:new 、invokestatic 、invokespecial 和两个invokevirtual ; 而testStringBuffer 方法只调用了两个invokevirtual 方法。第一个方法比第二个方法多做了好多工作,其效率当然是要低的。而且我们从java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  可以看出来其实对于String字符串合并,内部还是转化为StringBuilder的方法调用,这是因为String是长度不可变的,所以不如直接采用StringBuilder(与StringBuffer 长度都是可变的,只不过前者是非线程安全,后者是线程安全)进行字符串合并。

 

       

                                                                 使用Intellij idea如何快速查看Java类字节码
 

1、打开File-Settings

 

Java字节码深入解析  ||  使用Intellij idea如何快速查看Java类字节码_idea

2:打开Tools-External Tools,右侧点击绿色“+”

 

Java字节码深入解析  ||  使用Intellij idea如何快速查看Java类字节码_idea_02

3:填写一些内容规则:Name是在类中,右键时使用时的名称

Java字节码深入解析  ||  使用Intellij idea如何快速查看Java类字节码_工具_03

4:代码处右键,即可找到添加的功能

Java字节码深入解析  ||  使用Intellij idea如何快速查看Java类字节码_工具_04

 

5、通过jdk自带的反编译工具命令 javap 可以查看class文件的字节码信息

-verbose

或者

-c

都可以,详情可以查看javap命令如何使用

Java字节码深入解析  ||  使用Intellij idea如何快速查看Java类字节码_Java类字节码_05