JVM基础(1):java字节码文件-class文件到底是什么

本文介绍了Java字节码文件的基本概念,包括一次编译到处运行的特点、Class文件结构及属性。并通过具体示例深入分析了字节码文件的魔数、版本号、常量池等内容。同时,还探讨了字节码如何描述Java类的方法和变量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. java字节码文件

1. 一次编译,到处运行。

Cpu只能运行字节码,java代码必须要先编译成字节码文件(class文件),JVM才能运行。编译之后的文件交由给不同平台的虚拟机去读取执行,即一次编译到处运行。

JVM也不再只支持Java,由此衍生出了许多基于JVM的编程语言,如Groovy, Scala, Koltin等等。

在这里插入图片描述

 
 

2. java字节码文件 - Class文件

class文件本质上是一个以8字节为单位的二进制流,各种数据按照顺序排列在class文件中。
JVM根据规则解析该二进制数据得到相关类信息。

class文件的结构属性

数据类型解释
魔数每个class文件的头四个字节称为魔术,用于标识这是一个class文件
常量池常量池存储的资源有:变量/方法的类型、属性和名称等
访问标志类的属性和访问类型,比如该类是类还是接口,访问类型是否是public等
索引包括类索引、父类索引和接口索引,通过这些来确定类的继承关系
字段表属性描述类中声明的变量的属性。比如变量的作用域、是否是静态、可变性、数据类型等
方法表属性描述方法属性
属性表属性描述某些场景下专有的信息。比如字段表/方法表中特殊的属性

 

3. 看一个例子

3.1. java类

public class Main {
    private int m;
    public int inc() {
        return m + 1;
    }
}

 

3.2. javac Main.java 编译文件

cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0369 6e63
0100 0328 2949 0100 0a53 6f75 7263 6546
696c 6501 0009 4d61 696e 2e6a 6176 610c
0007 0008 0c00 0500 0601 0010 636f 6d2f
7268 7974 686d 372f 4d61 696e 0100 106a
6176 612f 6c61 6e67 2f4f 626a 6563 7400
2100 0300 0400 0000 0100 0200 0500 0600
0000 0200 0100 0700 0800 0100 0900 0000
1d00 0100 0100 0000 052a b700 01b1 0000
0001 000a 0000 0006 0001 0000 0003 0001
000b 000c 0001 0009 0000 001f 0002 0001
0000 0007 2ab4 0002 0460 ac00 0000 0100
0a00 0000 0600 0100 0000 0800 0100 0d00
0000 0200 0e
魔数和java版本号

魔数:文件开头的4个字节(“cafe babe”)称之为 魔数,唯有以"cafe babe"开头的class文件方可被虚拟机所接受,这4个字节就是字节码文件的身份识别。

java版本号:0000是编译器jdk版本的次版本号0,0034转化为十进制是52,是主版本号,java的版本号从45开始,除1.0和1.1都是使用45.x外,以后每升一个大版本,版本号加一。也就是说,编译生成该class文件的jdk版本为1.8.0。

 

3.3. 反编译看其他属性

继续往下查看其他属性, 通过工具反编译字节码文件继续去看。

javap -verbose -p Main.class
Classfile /E:/JavaCode/TestProj/out/production/TestProj/com/rhythm7/Main.class
  Last modified 2018-4-7; size 362 bytes
  MD5 checksum 4aed8540b098992663b7ba08c65312de
  Compiled from "Main.java"
public class com.rhythm7.Main
  minor version: 0
  major version: 52
  //类的访问标志:为public、允许使用invokespecial字节码指令的新语义.
  flags: ACC_PUBLIC, ACC_SUPER
  //常量池
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // com/rhythm7/Main.m:I
   #3 = Class              #20            // com/rhythm7/Main
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/rhythm7/Main;
  #14 = Utf8               inc
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               Main.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // m:I
  #20 = Utf8               com/rhythm7/Main
  #21 = Utf8               java/lang/Object
{
  private int m;
    descriptor: I
    flags: ACC_PRIVATE

  public com.rhythm7.Main();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/rhythm7/Main;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/rhythm7/Main;
}
SourceFile: "Main.java"

常量池:

主要存放两大类常量:字面量和符号引用。

字面量对应于java的常量。 字符串、final常量等
符号引用包含:类、接口的全限定名、字段/方法的名称和描述符。

JVM运行时,从常量池中获取符号引用,当类创建或运行时,解析并翻译到具体的内存地址,即动态链接。

//方法引用,指向了4,8个常量 ,最后拼接成右侧注释内容:其实是构造器的声明
#1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
#4 = Class              #21            // java/lang/Object
#7 = Utf8               <init>
#8 = Utf8               ()V
#18 = NameAndType        #7:#8          // "<init>":()V
#21 = Utf8               java/lang/Object

第#2常量,分析同理。

//I代表基础类型int
#2 = Fieldref           #3.#19         // com/rhythm7/Main.m:I
#3 = Class              #20            // com/rhythm7/Main
#5 = Utf8               m
#6 = Utf8               I
#19 = NameAndType        #5:#6          // m:I
#20 = Utf8               com/rhythm7/Main

在这里插入图片描述
对于数组类型,每一位使用一个前置的[字符来描述

如定义一个java.lang.String[][]类型的维数组,将被记录为[[Ljava/lang/String。

 

方法表集合
{
//成员变量
  private int m;
    descriptor: I
    flags: ACC_PRIVATE
//构造函数:main方法 返回值为void 公开方法
  public com.rhythm7.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
 //stack:最大操作数栈,运行时会根据这个值来分配栈帧,中操作栈的深度。
 //locals:局部变量所需空间,单位slot(4个字节)。locals的大小不是所有局部变量所占slot之和,因为slot是重用的。
 //args_size :方法参数个数。1:表示每个方法都会有一个隐藏参数。
 //方法体内容:0,1,4为字节码"行号"。代码意思是将第一个本地变量推至栈顶,然后执行实例方法,即注释里的,然后执行返回语句,结束方法。
 //LineNumberTable:描述源码行号与字节码行号(字节码偏移量)之间的对应关系。
 //LocalVariableTable:帧栈中局部变量与源码中定义的变量之间的关系。
 
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
      //start 表示该局部变量在哪一行开始可见,length表示可见行数,Slot代表所在帧栈位置,Name是变量名称,然后是类型签名。
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/rhythm7/Main;
//方法
  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
    //方法体内的内容是:将this入栈,获取字段#2并置于栈顶, 将int类型的1入栈,将栈内顶部的两个数值相加,返回一个int类型的值。
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/rhythm7/Main;
}

 

4. 再看一个

利用所学的知识点来分析一些Java问题:

public class TestCode {
    public int foo() {
        int x;
        try {
            x = 1;
            return x;
        } catch (Exception e) {
            x = 2;
            return x;
        } finally {
            x = 3;
        }
    }
}

查看字节码的foo方法内容:

public int foo();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=5, args_size=1
         0: iconst_1 //int型1入栈 ->栈顶=1
         1: istore_1 //将栈顶的int型数值存入第二个局部变量 ->局部2=1
         2: iload_1 //将第二个int型局部变量推送至栈顶 ->栈顶=1
         3: istore_2 //!!将栈顶int型数值存入第三个局部变量 ->局部3=1
         
         4: iconst_3 //int型3入栈 ->栈顶=3
         5: istore_1 //将栈顶的int型数值存入第二个局部变量 ->局部2=3
         6: iload_2 //!!将第三个int型局部变量推送至栈顶 ->栈顶=1
         7: ireturn //从当前方法返回栈顶int数值 ->1
         
         8: astore_2 // ->局部3=Exception
         9: iconst_2 // ->栈顶=2
        10: istore_1 // ->局部2=2
        11: iload_1 //->栈顶=2
        12: istore_3 //!! ->局部4=2
        
        13: iconst_3 // ->栈顶=3
        14: istore_1 // ->局部1=3
        15: iload_3 //!! ->栈顶=2
        16: ireturn // -> 2
        
        17: astore        4 //将栈顶引用型数值存入第五个局部变量=any
        19: iconst_3 //将int型数值3入栈 -> 栈顶3
        20: istore_1 //将栈顶第一个int数值存入第二个局部变量 -> 局部2=3
        21: aload         4 //将局部第五个局部变量(引用型)推送至栈顶
        23: athrow //将栈顶的异常抛出
      Exception table:
         from    to  target type
             0     4     8   Class java/lang/Exception //0到4行对应的异常,对应#8中储存的异常
             0     4    17   any //Exeption之外的其他异常
             8    13    17   any
            17    19    17   any

在字节码的4,5,以及13,14中执行的是同一个操作,就是将int型的3入操作数栈顶,并存入第二个局部变量。这正是我们源码在finally语句块中内容。也就是说,JVM在处理异常时,会在每个可能的分支都将finally语句重复执行一遍。

通过一步步分析字节码,可以得出最后的运行结果是:

不发生异常时: return 1
发生异常时: return 2
发生非Exception及其子类的异常,抛出异常,不返回值

 

5. 字节码增强技术 ing

 

 
https://pdai.tech/md/java/jvm/java-jvm-class.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值