IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 教你如何直接阅读一个java的字节码文件结构分析----附带逐字节码分析图 -> 正文阅读

[Java知识库]教你如何直接阅读一个java的字节码文件结构分析----附带逐字节码分析图

字节码文件概述

字节码文件是跨平台的吗?

Java 虚拟机不和包括 Java 在内的任何语言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联。

无论使用何种语言进行软件开发,只要能将源文件编译为正确的Class文件,那么这种语言就可以在Java虚拟机上执行。可以说,统一而强大的Class文件结构,就是Java虚拟机的基石、桥梁。

image-20211018104038228

想要让一个Java程序正确地运行在JVM中,Java源码就必须要被编译为符合JVM规范的字节码。

· 所有的JVM全部遵守Java虚拟机规范,也就是说所有的JVM环境都是一样的,这样一来字节码文件可以在各种JVM上运行。

从Java虚拟机的角度看,通过Class文件,可以让更多的计算机语言支持Java虚拟机平台。因此,Class文件结构不仅仅是Java虚拟机的执行入口,更是Java生态圈的基础和核心。

class文件里是什么?字节码文件里是什么?

image-20211018115647200

源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码。

随着Java平台的不断发展,在将来,Class文件的内容也一定会做进一步的扩充,但是其基本的格式和结构不会做重大调整。

能介绍下生成class文件的编译器吗?

1、从位置上理解

image-20211018115849056

前端编译器 vs 后端编译器

2. 前端编译器的种类

? Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器

? HotSpot VM并没有强制要求前端编译器只能使用javac来编译字节码,其实只要编译结果符合JVM规范都可以,被JVM所识别即可。

? 在Java的前端编译器领域,除了javac之外,还有一种被大家经常用到的前端编译器,那就是内置在Eclipse中的ECJ (Eclipse Compiler for Java)编译器。和Javac的全量式编译不同,ECJ是一种增量式编译器。

· 默认情况下,IntelliJ IDEA 使用 javac 编译器。(还可以自己设置为AspectJ编译器 ajc)

3. 前端编译器的任务

· 前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件。

目前前端编译器的局限性

? 前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器负责。

AOT(静态提前编译器,Ahead Of Time Compiler)

· jdk9引入了AOT编译器(静态提前编译器,Ahead Of Time Compiler)

· Java 9 引入了实验性 AOT 编译工具jaotc。它借助了 Graal (图形算法语言)编译器,将所输入的 Java 类文件转换为机器码,并存放至生成的动态共享库之中。

· 所谓 AOT 编译,是与即时编译相对立的一个概念。我们知道,即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而 AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

· .java -> .class -> .so

· 最大好处:Java虚拟机加载已经预编译成二进制库,可以直接执行。不必等待即时编译器的预热,减少Java应用给人带来“第一次运行慢”的不良体验。

· 缺点:

· 破坏了java“一次编译,到处运行”,必须为每个不同硬件、OS编译对应的发行包。

· 降低了Java链接过程的动态性,加载的代码在编译期就必须全部已知。

还需要继续优化中,最初只支持Linux x64 java base

哪些类型对应有Class的对象

类别:

(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

(2)interface:接口

(3)[]:数组

(4)enum:枚举

(5)annotation:注解@interface

(6)primitive type:基本数据类型

(7)void

		/**
     * 拥有Class对象的类型
     * @author wuwei
     */
    @Test
    public void test1() {
        //类
        Class c1 = Object.class;
        Class c2 = Test01.class;
        //接口
        Class c3 = Map.class;
        //数组
        Class c4 = String[].class;
        //枚举
        Class c5 = Month.class;
        //注解
        Class c6 = Test.class;
        //基本数据类型
        Class c7 = int.class;
        Class c8 = double.class;
        //void
        Class c9 = void.class;

        //测试不同长度数组的Class对象是否一致
        int[] a = new int[10];
        int[] b = new int[20];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        System.out.println(c10);
        System.out.println(c11);
        //只要元素的类型与维度一样,就是同一个Class
        System.out.println(c10 == c11); //true
    }

字节码指令

什么是字节码指令?

字节码指令(byte code)

Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。

代码如下:

image-20211019143514945

字节码指令如下

image-20211019143446143

为什么要懂字节码指令?

/**
     * 面试题 i++和++i有什么区别。
     */
    @Test
    public void test() {
        int i = 10;
        i++;
        //++i;
        System.out.println(i);
    }

    @Test
    public void test2() {
        int i = 10;
        i = i++;
        System.out.println(i);
    }

    @Test
    public void test3() {
        int i = 2;
        i *= i++; // i= i * i++
        System.out.println(i);//
    }

    @Test
    public void test4() {
        Integer  i1 = 10;
        Integer  i2 = 10;
        System.out.println(i1 == i2);//
        Integer  i3 = 128;
        Integer  i4 = 128;
        System.out.println(i3 == i4);//

        Boolean i5 = false;
        Boolean i6 = false;
        System.out.println(i5 == i6);
    }

    @Test
    public void test5(){
        String str = new String("hello") + new String("world");
        str.intern();
        String str1 = "helloworld";
        System.out.println(str==str1);

        String a3 = new String("AA");    //在堆上创建对象AA
        a3.intern(); //在常量池上创建对象AA的引用
        String a4 = "AA"; //常量池上存在引用AA,直接返回该引用指向的堆空间对象,即a3
        System.out.println(a3 == a4);
    }

上述代码:字节码指令解析

test2()

 0 bipush 10 -- 将常量int10放入操作数栈中
 2 istore_1 -- 将int从栈中取出存储到局部变量表中索引为1的位置
 3 iload_1 -- 从局部变量表中取出索引为1的int值放入操作数栈中
 4 iinc 1 by 1 -- 局部变量表中索引为1的int值自增1
 7 istore_1 -- 将操作数栈中的数据存入到局部变量表索引为1的位置
 8 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
11 iload_1 -- 加载局部变量表中索引为1的数据到操作数栈
12 invokevirtual #3 <java/io/PrintStream.println : (I)V> -- 打印
15 return

test3()

 0 iconst_2 -- 将常量int2压入到操作数栈中
 1 istore_1 -- 将操作数栈中的int取出存到局部变量表中索引为1的位置
 2 iload_1 -- 加载局部变量表索引为1int值压入到栈中
 3 iload_1 -- 同上(同一个数值取出了两次压到了栈中)
 4 iinc 1 by 1 -- 局部变量表中索引为1int值自增1
 7 imul -- 从栈中取出两个int值相乘再将结果压入到栈中
 8 istore_1 -- 将结果存入到局部变量表索引为1的位置
 9 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
12 iload_1 -- 将局部变量表索引为1int值加载压如到栈中
13 invokevirtual #3 <java/io/PrintStream.println : (I)V> -- 打印
16 return

test4()

 0 bipush 10 -- 压栈 
 2 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
 		-- 调用Integer.valueOf
 		-- 若值在-128127之间
 		-- 就将值从缓存中取出
 		-- 若没有就new一个新的Integer
 5 astore_1 -- 将引用存储到局部变量表中索引为1的位置
 6 bipush 10 -- 将int10压栈
 8 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;> -- 同上
11 astore_2 -- 将引用存储到局部变量表中索引为2的位置
12 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
15 aload_1 -- 将索引为1的引用入栈
16 aload_2 -- 将索引为2的引用入栈
17 if_acmpne 24 (+7) -- 比较栈顶两个引用,若不相等跳转到2420 iconst_1 --1压到栈中
21 goto 25 (+4) -- 跳转到地址为5的指令中
24 iconst_0
25 invokevirtual #5 <java/io/PrintStream.println : (Z)V> -- 打印布尔结果
28 sipush 128 --128值压栈
31 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
		-- 超出了缓存范围,new一个新的integer
34 astore_3 -- 将引用放入335 sipush 128 -- 压栈
38 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
		-- 超出范围  new新的
41 astore 4 -- 将引用放入到局部变量表索引为4的位置
43 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
46 aload_3 -- 将局部变量表索引3的引用取出后压栈
47 aload 4 -- 将局部变量表索引4的引用取出后压栈
49 if_acmpne 56 (+7) -- 比较栈顶的两个引用,不一致则跳转到位置为56的指令
52 iconst_1
53 goto 57 (+4)
56 iconst_0 --0放入到栈中
57 invokevirtual #5 <java/io/PrintStream.println : (Z)V> 打印结果
60 iconst_0 -- 将FALSE放入栈中
61 invokestatic #6 <java/lang/Boolean.valueOf : (Z)Ljava/lang/Boolean;>
64 astore 5 -- 将FALSE的引用存储到局部变量表索引为5的位置
66 iconst_0 -- 同上
67 invokestatic #6 <java/lang/Boolean.valueOf : (Z)Ljava/lang/Boolean;>
70 astore 6 -- 存到局部变量表6的位置
72 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
75 aload 5 -- 取出压栈
77 aload 6 -- 取出压栈
79 if_acmpne 86 (+7) -- 比较栈顶的引用
82 iconst_1 -- 相同
83 goto 87 (+4) -- 跳转到87位置的指令
86 iconst_0
87 invokevirtual #5 <java/io/PrintStream.println : (Z)V> -- 打印Boolean90 return

test5() 不看字节码了

1.只在常量池上创建常量
    String a1 = "AA";

2.只在堆上创建对象
    String a2 = new String("A") + new String("A");

3.在堆上创建对象,在常量池上创建常量
    String a3 = new String("AA");

4.在堆上创建对象,在常量池上创建引用
    String a4 = new String("A") + new String("A");//只在堆上创建对象AA
    a4.intern();//将该对象AA的引用保存到常量池上

5.在堆上创建对象,在常量池上创建常量,在常量池上创建引用(不可能)
    String a5 = new String("A") + new String("A");//只在堆上创建对象
    a5.intern();//在常量池上创建引用
    String a6 = "AA";//此时不会再在常量池上创建常量AA,而是将a5的引用返回给a6
    System.out.println(a5 == a6); //true

image-20211019143751990

举个例子:父子依赖字节码解析

class F1 {
    int x = 10;
    public F1() {
        //3.调用打印方法 此时这个this指的是new出来的对象 也就是Z1
        this.print();
        //4.给F1的x赋值  此时F的x=20
        x = 20;
    }
    public void print() {
        System.out.println("F1.x = " + x);
    }
}

class Z1 extends F1 {
    int x = 30;
    //2.执行空参构造方法 先执行父类的构造方法
    public Z1() {
        //5.再执行Z1的方法 此时 Z1的<init>方法已执行 此时Z1的x = 30
        this.print();
        //6.给Z1的x赋值  此时Z1的x=40
        x = 40;
    }

    public void print() {
        //3.1打印
        //此时x未被赋值(未执行Z1的 <init方法>),值为0
        //5.1 打印,当前值为30
        System.out.println("Z1.x = " + x);
    }
}
public class BateCode02 {
    public static void main(String[] args) {
        //1.创建变量f 执行Z1类的构造方法
        F1 f = new Z1();
        //7.打印 F1中的x 打印结果为20
        System.out.println(f.x);
    }
}

打印结果如下:

Z1.x = 0
Z1.x = 30
20
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:27:07  更:2021-11-12 19:28:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:40:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码