Jvm

JVM类字节码

2022-03-03  本文已影响0人  漆先生

原文链接:https://www.pdai.tech/md/java/jvm/java-jvm-class.html
源代码通过编译器编译为字节码,再被类加载子系统加载到JVM中运行。

一、多语言编译为字节码在JVM运行

计算机是不能直接运行java代码的,必须要先运行java虚拟机,再由java虚拟机运行编译后的java代码。这个编译后的java代码,就是字节码-class文件

在cpu层面来看,计算机中所有的操作都是一个个指令的运行汇集而成的,java是高级语言,计算机是无法识别的,所以java代码必须要先编译成字节码文件,jvm才能正确识别代码转换后的指令并将其运行。

二、Java字节码文件

class文件本质上是一个以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件中。jvm根据其特定的规则解析该二进制数据,从而得到相关信息。

Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表。

1.class文件的结构属性

image.png

2.魔数和class文件的版本

编写一个java文件

class Main {
    private int m;

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

通过javac Main.java命令生成class文件,再通过命令od -t x1 Main.class命令查看class文件。

0000000 ca fe ba be 00 00 00 34 00 13 0d 0a 00 04 00 0f
0000020 09 00 03 00 10 07 00 11 07 00 12 01 00 01 6d 01
0000040 00 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28
0000060 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65
0000100 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 03 69 6e
0000120 63 01 00 03 28 29 49 01 00 0d 0a 53 6f 75 72 63
0000140 65 46 69 6c 65 01 00 09 4d 61 69 6e 2e 6a 61 76
0000160 61 0c 00 07 00 08 0c 00 05 00 06 01 00 04 4d 61
0000200 69 6e 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f
0000220 62 6a 65 63 74 00 20 00 03 00 04 00 00 00 01 00
0000240 02 00 05 00 06 00 00 00 02 00 00 00 07 00 08 00
0000260 01 00 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a
0000300 b7 00 01 b1 00 00 00 01 00 0d 0a 00 00 00 06 00
0000320 01 00 00 00 01 00 01 00 0b 00 0c 00 01 00 09 00
0000340 00 00 1f 00 02 00 01 00 00 00 07 2a b4 00 02 04
0000360 60 ac 00 00 00 01 00 0d 0a 00 00 00 06 00 01 00
0000400 00 00 05 00 01 00 0d 0a 00 00 00 02 00 0e
0000416

3.反编译字节码文件

使用到java内置的一个反编译工具javap可以反编译字节码文件, 用法: javap <options> <classes>。options有如下选项:

-help --help -? 输出此用法消息 
version 版本信息 
-v -verbose 输出附加信息 
-l 输出行号和本地变量表 
-public 仅显示公共类和成员 
-protected 显示受保护的/公共类和成员 
-package 显示程序包/受保护的/公共类 和成员 (默认) 
-p -private 显示所有类和成员 
-c 对代码进行反汇编 
-s 输出内部类型签名 
-sysinfo 显示正在处理的类的 系统信息 (路径, 大小, 日期, MD5 散列) 
-constants 显示最终常量 
-classpath <path> 指定查找用户类文件的位置 
-cp <path> 指定查找用户类文件的位置 
-bootclasspath <path> 覆盖引导类文件的位

输入命令javap -v -p Main.class

Classfile /D:/project/androiddemo/Main.class
  Last modified 2022-3-3; size 265 bytes
  MD5 checksum 41d217cf3f555c8c1966a9df998fdf92
  Compiled from "Main.java"
class Main
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // Main.m:I
   #3 = Class              #17            // Main
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               Main.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               Main
  #18 = Utf8               java/lang/Object
{
  private int m;
    descriptor: I
    flags: ACC_PRIVATE

  Main();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  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 5: 0
}
SourceFile: "Main.java"

前面7行信息包括:Class文件当前所在位置,最后修改时间,文件大小,MD5值,编译自哪个文件,类的全限定名,jdk次版本号,主版本号。

然后紧接着的是该类的访问标志:ACC_PUBLIC, ACC_SUPER,访问标志的含义如下:


image.png

4.常量池

常量池(constant pool)可以理解成Class文件中的资源仓库。主要存放的是两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量类似于java中的常量概念,而符号引用则属于编译原理方面的概念,包括以下三种:

不同于C/C++, JVM是在加载Class文件的时候才进行的动态链接,也就是说这些字段和方法符号引用只有在运行期转换后才能获得真正的内存入口地址。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析并翻译到具体的内存地址。

第一个常量分析
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #4 = Class              #18            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
  #15 = NameAndType        #7:#8          // "<init>":()V
  #18 = Utf8               java/lang/Object

第一个常量是一个方法定义,指向了第4和第18个常量。以此类推查看第4和第18个常量。最后可以拼接成第一个常量右侧的注释内容:java/lang/Object."<init>":()V

第二个常量分析
   #2 = Fieldref           #3.#16         // Main.m:I
   #3 = Class              #17            // Main
   #5 = Utf8               m
   #6 = Utf8               I
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               Main

此处声明了一个字段m,类型为I, I即是int类型。关于字节码的类型对应如下:


image.png

对于数组类型,每一位使用一个前置的[字符来描述,如定义一个java.lang.String[][]类型的维数组,将被记录为[[Ljava/lang/String;

5.方法表集合

在常量池之后的是对类内部的方法描述,在字节码中以表的集合形式表现.。

  private int m;
    descriptor: I
    flags: ACC_PRIVATE

此处声明了一个私有变量m,类型为int,返回值为int。

 Main();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

构造方法:Main(),返回值为void, 访问标志没有(默认)。code内的主要属性为:

上一篇 下一篇

猜你喜欢

热点阅读