一、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是可运行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指令划分,也算是复杂指令集了。
4. 寄存器
JVM的指令集是基于栈,而不是基于寄存器的,其用到的寄存器很少。
- JAVA虚拟机中的寄存器是PC(程序运行计数器)
- 其它寄存器利用物理CPU的寄存器:堆栈指针寄存器、框架寄存器、变量寄存器。
JVM让物理CPU直接执行Java程序所对应的目标机器码,而不需要实现一个虚拟的CPU。让CPU执行java的代码时,需要对CPU场景进行上下文切换,这部分需要占用比较大的CPU资源。
JVM调用函数的时候,Java函数的代码并没有被存放到代码段中,而是被放在了一个code缓存中,每一个Java函数的代码块在这个code缓存中都会有一个索引位置,最终JVM会跳转到这个索引位置处执行Java函数调用。同时Java的函数一定是封装在类中的,因此JVM在执行函数调用时,还需要通过类寻址等一系列运算最终才能定位这个入口。
二、字节码文件格式
1. java编译器流程:
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>
对应的十六进制文件解析:
部分字段解释:
- u4 : MagicNumber 前4个字节 固定值 0xCAFEBABE
- 2个u2 : Version 版本号4个字节,跟在MagicNumber后面
- 1个u2 : Constant_pool 常量数量
- 接下来是常量池
- Fields+attributes 字段信息
- Methods + attributes 方法信息
3. 常量池
常量池的数据通常有两种类型:
- 字面量,如字符串、final修饰的常量
- 符号引用: 类/接口的全限定名、方法的名称和描述、字段的名称和描述等
三、反汇编字节码
下面的操作需要 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
编译就会显示反汇编的代码。
反汇编的示例:
可以看到字节码到汇编以后的代码数量是暴增的。
四、关于动态修改字节码
由于java的字节码文件是有明确规格的,所以可以通过对字节码的修改来动态的修改类,这就是AOP技术的核心。但直接对字节码进行修改非常困难,需要对字节码非常熟悉。一些第三方的库可以简化这种修改,如:
- ASM 基于 Java 字节码层面的代码分析和修改工具,可以直接产生二进制class文件,也可以在类被加载到JVM前动态改变类行为;
- javassist 开源的分析、编辑和创建Java字节码的类库,是jboss的一个子项目
- cglib 一个高性能的代码生成包,实现JDK的动态代理,被应用在Spring AOP、dynaop、Hibernate等框架中。
- BCEL Apache Byte Code Engineering Library,实现对字节码文件的功能扩充。