java虚拟机 — 字节码生成

1. 总体流程

简单来讲,一款编程语言想兼容底层(跨操作系统)的方式大概有两种:一是通过编译器,如C或C++。但是要针对不同硬件平台和操作系统开发不同的编译器,这样实现十分麻烦。二是通过中间语言,如Java、C#。代码被编译后生成中间语言,后由虚拟机负责解释和运行,虚拟机在运行期间将中间语言实时翻译成与特定底层平台匹配的机器指令并运行。
java语言声称可以“一次编译,到处运行(write once , run everywhere)”,那么具体是如何实现的呢?
主体流程大约如下:一个java代码文件,首先被编译器(javac)编译成一个特定格式的二进制字节码文件(class文件),然后被虚拟机(jvm)的加载器加载到内存中,再由执行引擎将将二进制字节码转换为可直接运行的本地机器码并运行。流程图如下:
这里写图片描述
JVM也会根据使用场景划分内存区域,当内存不足时会自动触发垃圾回收(GC)。为了将主机的性能最大化发挥,有时我们还需要进行JVM调优。
下面依次对上面的内容进行展开说明。

2. 字节码生成

通过上面的简述我们已经知道,java语言中是用过javac程序将java文件编译成class文件的,编译的过程大约分为如下四步:
(1)词法分析器:将代码(字符流)中关键字和标识符等内容解析转换成Token流,token流就是一组对应源码字符集和的单词序列;
(2)语法分析器:解析Token流中package关键字声明、import关键字声明和class主体信息,组建成更加结构话的语法树;
(3)语义分析器:对语法树进一步处理,包括添加默认构造函数、检查变量初始化、常量合并处理、检查操作变量类型是否匹配、检查操作语句是否可达、异常是否捕获或抛出、接触java语法糖等;
(4)代码生成器:调用com.sun.tools.javac.jvm.Gen类遍历语法树,生成最终字节码;
过程图如下:
这里写图片描述

3. Class文件结构

Class文件是一组以8位字节为基础范围的二进制刘,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。一个Class文件主要包含如下内容:

魔数和Class文件版本

头4个字节为魔数,0xCAFEBABE。
紧跟着魔数的4个字节存储的是Class文件的版本号,其中第7、8位保存的是主版本号。

常量池

可理解为Class文件之中的资源仓库,由于常量池中常量的数量是不固定的,所以在常量池入口放置一个u2类型的数据,代表常量池计数值。常量池中主要存放两类常量:字面量和符号引用。字面量即常量,符号引用包含类和接口的全限定名、字段的名称和描述符和方法的名称和描述符。

访问标识

紧跟在常量池后的2个字节,标识一些类或接口层次的访问信息,如:是类还是接口、是否为public类型、是否为abstract类型,是否为final等。

类索引、父索引与接口索引集合

类索引和父索引都是u2类型,而接口索引是一组u2类型数据集合。class文件用这三个数据来确定这个类的继承关系。

字段表集合

字段表用于描述接口或者声明的变量,字段包括类变量和实例变量,但不包括局部变量。

方法表集合

结构和字段表一致,保存方法的属性。注意,不包含方法的代码。

属性表集合

包含若干个属性。其中,
code属性储存方法的代码,
Exceptions属性存储throws关键字后的列举的异常,
lineNumberTablee属性存储java源码和字节码行号的对应关系,
localVariableTable属性记录栈帧中局部变量表中的变量和java源码中定义的变量之间的关系,
sourceFile属性记录生成这个class文件的源码文件名称,
ConstantValue属性通知JVM自动为静态变量赋值,
InnerClasses属性记录内部类信息。

4. 动态编程与静态编程


静态编程:类型检查是在编译时完成的。

动态编程:类型检查是在运行时完成的。

所谓动态编程就是绕过编译过程在运行时进行操作的技术,在Java中有如下几种方式:

反射
这个搞Java的应该比较熟悉,原理也就是通过在运行时获得类型信息然后做相应的操作。

动态编译
动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行。

调用JavaScript引擎
Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本。这个脚本API允许你为脚本语言提供Java支持。

动态生成字节码
这种技术通过操作Java字节码的方式在JVM中生成新类或者对已经加载的类动态添加元素。

操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit 。

ASM :直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。

Javassit 提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

应用层面来讲一般使用建议优先选择Javassit,如果后续发现Javassit 成为了整个应用的效率瓶颈的话可以再考虑ASM.当然如果开发的是一个基础类库,或者基础平台,还是直接使用ASM吧,相信从事这方面工作的开发者能力应该比较高。

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java虚拟可以读取字节码文件并将其转换成可执行的代码。字节码文件是Java源代码编译后生成的二进制文件,它包含了一系列指令,这些指令被Java虚拟解释和执行。通过这种方式,Java程序可以在不同的硬件平台和操作系统上运行,实现了"Write Once, Run Anywhere"的目标。 Java虚拟读取字节码文件的过程可以简单概括为以下几个步骤: 1. 加载:Java虚拟通过类加载器加载字节码文件,将其转换为运行时的类对象。类加载器负责查找并加载类文件,并将其转换为内存中的类对象。 2. 验证:在加载字节码文件后,Java虚拟会对字节码文件进行验证,确保其符合Java语言规范和虚拟规范。验证过程包括对字节码文件的结构、语义和安全性进行检查。 3. 准备:在验证通过后,Java虚拟会为类变量(静态变量)分配内存,并设置默认初始值。此时,还没有执行任何Java代码。 4. 解析:在准备阶段之后,Java虚拟会对字节码文件中的符号引用进行解析,将其转换为直接引用。这个过程将类或接口的符号引用解析为实际的内存地址。 5. 初始化:在准备阶段之后,Java虚拟会执行类的初始化操作,包括执行静态初始化块和静态变量的赋值操作。在这个阶段,Java程序的主方法会被调用,程序开始执行。 通过以上步骤,Java虚拟可以读取字节码文件并执行其中的指令,实现Java程序的运行。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Java 进阶:实例详解 Java 虚拟字节码指令](https://blog.csdn.net/m0_54853420/article/details/126104672)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值