JVM(一) Class文件的介绍和解析

本文详细介绍了JVM加载Class文件的过程,解析了Class文件的结构,并通过示例对比了String与StringBuffer在字节码层面的差异。通过对字节码的分析,揭示了方法调用和字符串拼接的底层实现,同时也说明了字节码技术在程序优化和增强中的应用。
摘要由CSDN通过智能技术生成

一、JVM加载过程

openJDK源码:http://hg.openjdk.java.net/jdk8u

二、Class文件

2.1 什么是class文件

1、Class字节码文件是jvm认识的一种文件,里面的地址都是逻辑的地址。最后需要运行在操作系统中,操作系统只能识别真实的物理地址。此时需要动态链接,就是在运行时动态地绑定对象、对象。
2、Class字节码文件是一组以8位字节为基础单位的二进流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何的分隔符,这使得整个Class文件存储的内容几乎全部是程序运行的必需数据。
3、Class字节码文件只有两种数据类型:无符号数(u)和表(info)

2.2 解析class文件

 查看class文件结构命令:javap -verbose StringTest.class

下图是对class文件转成16进制的部分结构图

                                                                       图2.2.1

 根据官网class规范(网址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html)可获得下图:

图2.2.2

 16进制     10进制
ca fe ba be            代表class文件  
0000            0       代表class文件副版本号   minor version: 0
0033            51      主版本号               major version: 51
004c        	76	   常量池个数(Constant pool长度+1)             constant_pool[constant_pool_count-1];

public class com.naixue.vip.p6.jvm.bytecode.StringTest
  minor version: 0
  major version: 51
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #17                         // com/naixue/vip/p6/jvm/bytecode/StringTest
  super_class: #18                        // java/lang/Object
  interfaces: 0, fields: 0, methods: 5, attributes: 1
Constant pool:
   #1 = Methodref          #18.#45        // java/lang/Object."<init>":()V
   #2 = String             #46            //
   #3 = Class              #47            // java/lang/StringBuilder
   #4 = Methodref          #3.#45         // java/lang/StringBuilder."<init>":()V
   #5 = Methodref          #3.#48         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #6 = String             #49            // test,
   #7 = Methodref          #3.#50         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Methodref          #53.#54        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = Class              #55            // java/lang/StringBuffer
  #11 = Methodref          #10.#45        // java/lang/StringBuffer."<init>":()V
  #12 = Methodref          #10.#56        // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  #13 = Methodref          #53.#57        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #14 = String             #58            // hello
  #15 = String             #59            // samuel
  #16 = Methodref          #17.#60        // com/naixue/vip/p6/jvm/bytecode/StringTest.test3:()Ljava/lang/String;
  #17 = Class              #61            // com/naixue/vip/p6/jvm/bytecode/StringTest
  #18 = Class              #62            // java/lang/Object
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               LocalVariableTable
  #24 = Utf8               this
  #25 = Utf8               Lcom/naixue/vip/p6/jvm/bytecode/StringTest;
  #26 = Utf8               test1
  #27 = Utf8               i
  #28 = Utf8               I
  #29 = Utf8               str
  #30 = Utf8               Ljava/lang/String;
  #31 = Utf8               StackMapTable
  #32 = Class              #63            // java/lang/String
  #33 = Utf8               test2
  #34 = Utf8               Ljava/lang/StringBuffer;
  #35 = Class              #55            // java/lang/StringBuffer
  #36 = Utf8               test3
  #37 = Utf8               ()Ljava/lang/String;
  #38 = Class              #64            // java/lang/Throwable
  #39 = Utf8               main
  #40 = Utf8               ([Ljava/lang/String;)V
  #41 = Utf8               args
  #42 = Utf8               [Ljava/lang/String;
  #43 = Utf8               SourceFile
  #44 = Utf8               StringTest.java
  #45 = NameAndType        #19:#20        // "<init>":()V
  #46 = Utf8
  #47 = Utf8               java/lang/StringBuilder
  #48 = NameAndType        #65:#66        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #49 = Utf8               test,
  #50 = NameAndType        #67:#37        // toString:()Ljava/lang/String;
  #51 = Class              #68            // java/lang/System
  #52 = NameAndType        #69:#70        // out:Ljava/io/PrintStream;
  #53 = Class              #71            // java/io/PrintStream
  #54 = NameAndType        #72:#73        // println:(Ljava/lang/String;)V
  #55 = Utf8               java/lang/StringBuffer
  #56 = NameAndType        #65:#74        // append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  #57 = NameAndType        #72:#75        // println:(Ljava/lang/Object;)V
  #58 = Utf8               hello
  #59 = Utf8               samuel
  #60 = NameAndType        #36:#37        // test3:()Ljava/lang/String;
  #61 = Utf8               com/naixue/vip/p6/jvm/bytecode/StringTest
  #62 = Utf8               java/lang/Object
  #63 = Utf8               java/lang/String
  #64 = Utf8               java/lang/Throwable
  #65 = Utf8               append
  #66 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #67 = Utf8               toString
  #68 = Utf8               java/lang/System
  #69 = Utf8               out
  #70 = Utf8               Ljava/io/PrintStream;
  #71 = Utf8               java/io/PrintStream
  #72 = Utf8               println
  #73 = Utf8               (Ljava/lang/String;)V
  #74 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuffer;
  #75 = Utf8               (Ljava/lang/Object;)V

                                                                        图2.2.3

由上面三图可解析:

u4代表4个字节ca fe ba be代表class文件  
u2 代表5,6字节0000转十进制为0代表class文件副版本号   minor version: 0
u2 代表7,8字节0033转十进制为51代表class主版本号 major version: 51
u2 代表9,10字节004c转十进制为76 代表class常量池个数,但由上图只能看到常量池个数为75,因为索引0位置是虚拟机预留的,不可使用。

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

tag是标识,根据本小节的字节码文件可得0a转十进制为10。tag标识对应结构如下图:

10代表是CONSTANT_Methodref_info的结构,如下:

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;               0012转十进制18   
    u2 name_and_type_index;       002d转十进制45
}

可由图2.2.3中,看到Methodref,同时有2个值分别的18和45,和上面的解析相对应

#1 = Methodref          #18.#45        // java/lang/Object."<init>":()

class文件解析作用:可以做字节码增强,可通过cglib(通过asm实现),aspectj,java Proxy,javassist。比如一些jvm调优工具,一些挂载,热部署的实现也是通过字节码技术来实现。

2.3 从字节码上看String和StringBuffer的区别

/**
 * 格式:操作码+操作数 istore_1
 * 加载和存储执行
 * iload_X:从局部变量表中加载int类型的数据到操作数栈 lload fload dload aload
 * ldc ldc2 ldc_w bipush。。。常量加载到操作数栈
 * 对象的创建和访问指令
 * new
 * newarray  XXXarray
 * getfield pupfild getstatic putstatic
 * 控制转移指令
 * ifeq iflt ifle ifgt
 * goto goto_w
 * 方法调用指令
 * invokevirtual 调用实例方法
 * invokestatic 调用静态方法
 * invokeintface
 * invokeXXXX
 */
    public static void test1(){
        String str = "";
        for (int i=0;i<10;i++){
            str = str+"test,";
        }
        System.out.println(str);

    }

    public static void test2(){
        StringBuffer str = new StringBuffer();
        for (int i=0;i<10;i++){
            str.append("test,");
        }
        System.out.println(str);

    }
public static void test1();
    descriptor: ()V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0          //stack栈的深度,locals本地变量表的长度,                                                                            args_size 形参列表的长度
         0: ldc           #2                  // String
         2: astore_0
         3: iconst_0
         4: istore_1
         5: iload_1
         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_0
        19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: ldc           #6                  // String test,
        24: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        27: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        30: astore_0
        31: iinc          1, 1
        34: goto          5
        37: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        40: aload_0
        41: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        44: return
      LineNumberTable:
        line 7: 0
        line 8: 3
        line 9: 11
        line 8: 31
        line 11: 37
        line 13: 44
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5      32     1     i   I
            3      42     0   str   Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 5
          locals = [ class java/lang/String, int ]
        frame_type = 250 /* chop */
          offset_delta = 31



public static void test2();
    descriptor: ()V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: new           #10                 // class java/lang/StringBuffer
         3: dup
         4: invokespecial #11                 // Method java/lang/StringBuffer."<init>":()V
         7: astore_0
         8: iconst_0
         9: istore_1
        10: iload_1
        11: bipush        10
        13: if_icmpge     29
        16: aload_0
        17: ldc           #6                  // String test,
        19: invokevirtual #12                 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        22: pop
        23: iinc          1, 1
        26: goto          10
        29: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        32: aload_0
        33: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        36: return
      LineNumberTable:
        line 16: 0
        line 17: 8
        line 18: 16
        line 17: 23
        line 20: 29
        line 22: 36
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10      19     1     i   I
            8      29     0   str   Ljava/lang/StringBuffer;
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 10
          locals = [ class java/lang/StringBuffer, int ]
        frame_type = 250 /* chop */
          offset_delta = 18

上面三图分别展示了

1、方法中一些常用指令格式的解析。

2、String和StringBuffer的测试代码

3、2中代码的字节码文件结构

可以看到test1()方法使String进行拼接时,每次拼接都会创建一个StringBuffer对象进行拼接,而test2()只会创建一个StringBuffer对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值