JVM概述与字节码

JVM概述与字节码


一、JAVA程序运行机制

1. 高级语言的运行机制

  • 编译型:c 、C++、FORTRAN、Pascal等
  • 解释型:Ruby、Python
  • 编译+解释型:Visual
    Basic,半编译型语言,首先被编译为P-代码,并将解释引擎封装在可执行性程序内,当运行程序时,P-代码会被解析成真正的二进制代码。Visual
    Basic编译的EXE文件中,既有程序的启动代码,也有链接解释程序的代码,而这部分代码负责启动Visual
    Basic解释程序,再对VisualBasic代码进行解释并执行。
  • 虚拟机:Java/Groovy/Scala,.Net,

2. Java程序的运行机制和JVM

Java语言比较特殊,程序运行的窗口是JVM虚拟机(Java Virutal Machine)。

JVM概述与字节码


JVM是可运行Java字节文件的虚拟计算机。所有平台上的JVM向编译器提供相同的编程接口,而编译器只需要面向虚拟机,生成虚拟机能理解的字节码(可以理解成JVM使用的机器码),然后由虚拟机来解释执行。

在一些虚拟机的实现中,还会使用just-in-time的编译器将虚拟机代码进行进一步的编译,转换成特定系统的机器码执行,从而提高执行效率。

已故的Sun公司制定的Java虚拟机规范在技术上规定了JVM的统一标准,具体定义了JVM的如下细节:

  • 指令集
  • 寄存器
  • 类文件的格式
  • 垃圾回收堆
  • 存储区

可以看出虚拟机实现了一个比较完整的操作系统内核,

3. JVM指令集

JVM的指令集目前的官网文档在:
https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-6.html
JAVA 指令集目前有2百条左右指令(8bit,总数不会超过255个),如果按物理的CPU指令划分,也算是复杂指令集了。


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


JVM概述与字节码


4. 寄存器

JVM的指令集是基于栈,而不是基于寄存器的,其用到的寄存器很少。

  • JAVA虚拟机中的寄存器是PC(程序运行计数器)
  • 其它寄存器利用物理CPU的寄存器:堆栈指针寄存器、框架寄存器、变量寄存器。

JVM让物理CPU直接执行Java程序所对应的目标机器码,而不需要实现一个虚拟的CPU。让CPU执行java的代码时,需要对CPU场景进行上下文切换,这部分需要占用比较大的CPU资源。

JVM调用函数的时候,Java函数的代码并没有被存放到代码段中,而是被放在了一个code缓存中,每一个Java函数的代码块在这个code缓存中都会有一个索引位置,最终JVM会跳转到这个索引位置处执行Java函数调用。同时Java的函数一定是封装在类中的,因此JVM在执行函数调用时,还需要通过类寻址等一系列运算最终才能定位这个入口。

二、字节码文件格式

1. java编译器流程:

JVM概述与字节码

2. 查看字节码示例

写一段简单的Java程序:

<code>package com.xundh;

public class Main {

    public static void main(String[] args) {
        int c=add(1,2);
        System.out.println("Hello World");
        System.out.println("c="+c);
    }
    public static int add(int a,int b){
        return a+b;
    }
}
/<code>

执行下面命令:

<code>javac Main.java
javap -verbose Main.class/<code>

分析字节码文件后输出如下内容:

<code>D:\Documents\Downloads\learn-java\src\com\xundh>javap -verbose Main.class
Classfile /D:/Documents/Downloads/learn-java/src/com/xundh/Main.class
  Last modified 2020-9-15; size 947 bytes
  MD5 checksum 063ed126168378bb07d6610d353ad144
  Compiled from "Main.java"
public class com.xundh.Main
  minor version: 0                             // 子版本
  major version: 55                           // 主版本
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#19         // java/lang/Object."":()V
   #2 = Methodref          #7.#20         // com/xundh/Main.add:(II)I                  add方法
   #3 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = String             #23            // Hello World
   #5 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = InvokeDynamic      #0:#29         // #0:makeConcatWithConstants:(I)Ljava/lang/String;
   #7 = Class              #30            // com/xundh/Main
   #8 = Class              #31            // java/lang/Object
   #9 = Utf8               
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               main                     // 方法 
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               add                       // 方法
  #16 = Utf8               (II)I
  #17 = Utf8               SourceFile            // 源文件名
  #18 = Utf8               Main.java
  #19 = NameAndType        #9:#10         // "":()V
  #20 = NameAndType        #15:#16        // add:(II)I
  #21 = Class              #32            // java/lang/System
  #22 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World
  #24 = Class              #35            // java/io/PrintStream
  #25 = NameAndType        #36:#37        // println:(Ljava/lang/String;)V
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       #6:#38         // invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lan
g/Object;)Ljava/lang/invoke/CallSite;
  #28 = String             #39            // c=╔
  #29 = NameAndType        #40:#41        // makeConcatWithConstants:(I)Ljava/lang/String;
  #30 = Utf8               com/xundh/Main
  #31 = Utf8               java/lang/Object
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               println
  #37 = Utf8               (Ljava/lang/String;)V
  #38 = Methodref          #42.#43        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Lja
va/lang/invoke/CallSite;
  #39 = Utf8               c=╔
  #40 = Utf8               makeConcatWithConstants
  #41 = Utf8               (I)Ljava/lang/String;
  #42 = Class              #44            // java/lang/invoke/StringConcatFactory
  #43 = NameAndType        #40:#48        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #44 = Utf8               java/lang/invoke/StringConcatFactory
  #45 = Class              #50            // java/lang/invoke/MethodHandles$Lookup
  #46 = Utf8               Lookup
  #47 = Utf8               InnerClasses
  #48 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #49 = Class              #51            // java/lang/invoke/MethodHandles
  #50 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #51 = Utf8               java/lang/invoke/MethodHandles
{
  public com.xundh.Main();
    descriptor: ()V
    flags: ACC_PUBLIC                  // 类的访问权限
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_1                                 // 前面是偏移地址,后面是操作指令 
         1: iconst_2
         2: invokestatic  #2                  // Method add:(II)I   调用静态函数
         5: istore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: ldc           #4                  // String Hello World
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: iload_1
        18: invokedynamic #6,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: return
      LineNumberTable:                 // 源码与字节码对应关系 
        line 6: 0
        line 7: 6
        line 8: 14
        line 9: 26

  public static int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 11: 0
}
SourceFile: "Main.java"
InnerClasses:
     public static final #46= #45 of #49; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #27 invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite
;
    Method arguments:
      #28 c=╔

/<code> 

对应的十六进制文件解析:

JVM概述与字节码

JVM概述与字节码


部分字段解释:

  • u4 : MagicNumber 前4个字节 固定值 0xCAFEBABE
  • 2个u2 : Version 版本号4个字节,跟在MagicNumber后面
  • 1个u2 : Constant_pool 常量数量
  • 接下来是常量池
  • Fields+attributes 字段信息
  • Methods + attributes 方法信息


3. 常量池

常量池的数据通常有两种类型:

  • 字面量,如字符串、final修饰的常量
  • 符号引用: 类/接口的全限定名、方法的名称和描述、字段的名称和描述等
JVM概述与字节码

三、反汇编字节码

下面的操作需要 hsdis的支持,下载地址:
https://sourceforge.net/projects/fcml/files/fcml-1.1.3/
把dll放到 \jdk1.8.0_31\jre\bin\server 目录下。
在IDEA中设置 VM options:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
编译就会显示反汇编的代码。

JVM概述与字节码


反汇编的示例:

JVM概述与字节码


可以看到字节码到汇编以后的代码数量是暴增的。

四、关于动态修改字节码

由于java的字节码文件是有明确规格的,所以可以通过对字节码的修改来动态的修改类,这就是AOP技术的核心。但直接对字节码进行修改非常困难,需要对字节码非常熟悉。一些第三方的库可以简化这种修改,如:

  • ASM 基于 Java 字节码层面的代码分析和修改工具,可以直接产生二进制class文件,也可以在类被加载到JVM前动态改变类行为;
  • javassist 开源的分析、编辑和创建Java字节码的类库,是jboss的一个子项目
  • cglib 一个高性能的代码生成包,实现JDK的动态代理,被应用在Spring AOP、dynaop、Hibernate等框架中。
  • BCEL Apache Byte Code Engineering Library,实现对字节码文件的功能扩充。


分享到:


相關文章: