导航:首页 > 编程语言 > 基于java下的简单编译器的实现

基于java下的简单编译器的实现

发布时间:2024-12-29 18:09:17

❶ 谁能简单阐述下java编译执行的过程

Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。本文首先简要介绍从Java文件的编译到最终执行的过程,随后对JVM规格描述作一说明。

一.Java源文件的编译、下载、解释和执行
Java应用程序的开发周期包括编译、下载、解释和执行几个部分。Java编译程序将Java源程序翻译为JVM可执行代码?字节码。这一编译过程同C/C++的编译有些不同。当C编译器编译生成一个对象的代码时,该代码是为在某一特定硬件平台运行而产生的。因此,在编译过程中,编译程序通过查表将所有对符号的引用转换为特定的内存偏移量,以保证程序运行。Java编译器却不将对变量和方法的引用编译为数值引用,也不确定程序执行过程中的内存布局,而是将这些符号引用信息保留在字节码中,由解释器在运行过程中创立内存布局,然后再通过查表来确定一个方法所在的地址。这样就有效的保证了Java的可移植性和安全性。

运行JVM字节码的工作是由解释器来完成的。解释执行过程分三部进行:代码的装入、代码的校验和代码的执行。装入代码的工作由"类装载器"(class loader)完成。类装载器负责装入运行一个程序需要的所有代码,这也包括程序代码中的类所继承的类和被其调用的类。当类装载器装入一个类时,该类被放在自己的名字空间中。除了通过符号引用自己名字空间以外的类,类之间没有其他办法可以影响其他类。在本台计算机上的所有类都在同一地址空间内,而所有从外部引进的类,都有一个自己独立的名字空间。这使得本地类通过共享相同的名字空间获得较高的运行效率,同时又保证它们与从外部引进的类不会相互影响。当装入了运行程序需要的所有类后,解释器便可确定整个可执行程序的内存布局。解释器为符号引用同特定的地址空间建立对应关系及查询表。通过在这一阶段确定代码的内存布局,Java很好地解决了由超类改变而使子类崩溃的问题,同时也防止了代码对地址的非法访问。

随后,被装入的代码由字节码校验器进行检查。校验器可发现操作数栈溢出,非法数据类型转化等多种错误。通过校验后,代码便开始执行了。

Java字节码的执行有两种方式:
1.即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。
2.解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。
通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作

具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。

二.JVM规格描述
JVM的设计目标是提供一个基于抽象规格描述的计算机模型,为解释程序开发人员提很好的灵活性,同时也确保Java代码可在符合该规范的任何系统上运行。JVM对其实现的某些方面给出了具体的定义,特别是对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括操作码和操作数的语法和数值、标识符的数值表示方式、以及Java类文件中的Java对象、常量缓冲池在JVM的存储映象。这些定义为JVM解释器开发人员提供了所需的信息和开发环境。Java的设计者希望给开发人员以随心所欲使用Java的自由。

JVM定义了控制Java代码解释执行和具体实现的五种规格,它们是:
JVM指令系统
JVM寄存器
JVM栈结构
JVM碎片回收堆
JVM存储区

2.1JVM指令系统

JVM指令系统同其他计算机的指令系统极其相似。Java指令也是由 操作码和操作数两部分组成。操作码为8位二进制数,操作数进紧随在操作码的后面,其长度根据需要而不同。操作码用于指定一条指令操作的性质(在这里我们采用汇编符号的形式进行说明),如iload表示从存储器中装入一个整数,anewarray表示为一个新数组分配空间,iand表示两个整数的"与",ret用于流程控制,表示从对某一方法的调用中返回。当长度大于8位时,操作数被分为两个以上字节存放。JVM采用了"big endian"的编码方式来处理这种情况,即高位bits存放在低字节中。这同 Motorola及其他的RISC CPU采用的编码方式是一致的,而与Intel采用的"little endian "的编码方式即低位bits存放在低位字节的方法不同。

Java指令系统是以Java语言的实现为目的设计的,其中包含了用于调用方法和监视多先程系统的指令。Java的8位操作码的长度使得JVM最多有256种指令,目前已使用了160多种操作码。

2.2JVM指令系统

所有的CPU均包含用于保存系统状态和处理器所需信息的寄存器组。如果虚拟机定义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行访问,这有利于提高运行速度。然而,如果虚拟机中的寄存器比实际CPU的寄存器多,在实现虚拟机时就会占用处理器大量的时间来用常规存储器模拟寄存器,这反而会降低虚拟机的效率。针对这种情况,JVM只设置了4个最为常用的寄存器。它们是:
pc程序计数器
optop操作数栈顶指针
frame当前执行环境指针
vars指向当前执行环境中第一个局部变量的指针
所有寄存器均为32位。pc用于记录程序的执行。optop,frame和vars用于记录指向Java栈区的指针。

2.3JVM栈结构

作为基于栈结构的计算机,Java栈是JVM存储信息的主要方法。当JVM得到一个Java字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:
局部变量
执行环境
操作数栈

局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。
执行环境用于保存解释器对Java字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。执行环境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。
操作数栈用于存储运算所需操作数及运算的结果。

2.4JVM碎片回收堆

Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。
在Java语言中,除了new语句外没有其他方法为一对象申请和释放内存。对内存进行释放和回收的工作是由Java运行系统承担的。这允许Java运行系统的设计者自己决定碎片回收的方法。在SUN公司开发的Java解释器和Hot Java环境中,碎片回收用后台线程的方式来执行。这不但为运行系统提供了良好的性能,而且使程序设计人员摆脱了自己控制内存使用的风险。

2.5JVM存储区

JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。对于这两种存储区域具体实现方式在JVM规格中没有明确规定。这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。

JVM是为Java字节码定义的一种独立于具体平台的规格描述,是Java平台独立性的基础。目前的JVM还存在一些限制和不足,有待于进一步的完善,但无论如何,JVM的思想是成功的。

对比分析:如果把Java原程序想象成我们的C++原程序,Java原程序编译后生成的字节码就相当于C++原程序编译后的80x86的机器码(二进制程序文件),JVM虚拟机相当于80x86计算机系统,Java解释器相当于80x86CPU。在80x86CPU上运行的是机器码,在Java解释器上运行的是Java字节码。

Java解释器相当于运行Java字节码的“CPU”,但该“CPU”不是通过硬件实现的,而是用软件实现的。Java解释器实际上就是特定的平台下的一个应用程序。只要实现了特定平台下的解释器程序,Java字节码就能通过解释器程序在该平台下运行,这是Java跨平台的根本。当前,并不是在所有的平台下都有相应Java解释器程序,这也是Java并不能在所有的平台下都能运行的原因,它只能在已实现了Java解释器程序的平台下运行。

❷ JAVA的编译器有哪些

JAVA的编译器主要有以下几种:


1. javac:这是Java开发工具箱(JDK)中自带的编译器,可以将.java源文件编译成.class字节码文件。javac编译器是Java语言的核心编译器,具有稳定、高效的特点,是Java开发人员最常用的编译器。


2. Eclipse编译器:Eclipse是一款流行的Java集成开发环境(IDE),内置了Java编译器,可以直接在Eclipse中编写Java代码并进行编译、调试。Eclipse编译器提供了丰富的开发工具和插件,提高了Java开发的效率。


3. IntelliJ IDEA编译器:IntelliJ IDEA是另一款广泛使用的Java IDE,也内置了Java编译器。IntelliJ IDEA提供了智能代码编辑、重构、调试等功能,提高了Java代码的质量和可维护性。


这些编译器各有优缺点,开发人员可以根据自己的需求和习惯选择合适的编译器。例如,如果只需要简单的编译功能,可以使用javac;如果需要丰富的开发工具和高效的开发环境,可以选择Eclipse或IntelliJ IDEA。同时,不同的编译器可能对Java语言的语法和特性支持有所不同,需要根据具体情况进行选择。


总的来说,Java的编译器是Java开发中的重要组成部分,选择合适的编译器可以提高开发效率和代码质量。

❸ 用Java可以写C语言编译器吗

可以的,编译器就是一种把文本(源码)按语言语义的规则翻译成字节码的一套程序。内
C通常不容像Java跨平台,确定目标平台是什么,比如是x86、ARMS、MIPS、JVM,操作系统环境是什么,然后编写输出生成所在环境下能运行的字节码。
用任何语言工具写都可以。 各语言开发工具间只有开发效率的差异,没有可行不可行的区别。

❹ 一种简单的热部署方式结合动态编译实现java文件热替换

前言日常开发中常常遇到这样的场景:

1、测试环境查证问题,想加日志来辅助查证,之前人们都是使用塞包的方式。使用这种方式还得重启,一些应用启动耗时也比较久。

2、测试环境联调,一些数据测试环境不一定造的出来,需要后台写死,这种如果采用提交代码的方式,复杂程度大,也容易被带上线,风险高。

如何解决:一、初级阶段:

采用代码热更新的方式,这种如果只是涉及一些添加日志,或者在方法内部做一些微小的改动,那么可以采用jvm提供的Instrumentation接口,通过该接口可以实现初级的热部署。

例如:

public static void agentmain(String agentArgs, Instrumentation inst) {// 从 agentArgs 获取外部参数System.out.println("开始热更新代码");String path = agentArgs;System.out.println("路径为:" + path);PrintStream out;try {RandomAccessFile f = new RandomAccessFile(path, "r");final byte[] bytes = new byte[(int) f.length()];f.readFully(bytes);final String clazzName = readClassName(bytes);// 加载for (Class clazz : inst.getAllLoadedClasses()) {//System.out.println("========hotswap=========" + clazz.getName());if (clazz.getName().equals(clazzName)) {ClassDefinition definition = new ClassDefinition(clazz, bytes);inst.redefineClasses(definition);}}} catch (UnmodifiableClassException | IOException | ClassNotFoundException e) {System.out.println("热更新数据失败");}}二、进阶阶段

一般情况下使用初级阶段就可以解决大部分问题,但是有一些比较老旧的代码可能开发人员本地编译环境都没有,没有办法生成class文件,那么这种时候就需要引入动态编译。

动态编译的原理是采用JavaCompiler的方式来实现内存编译。核心思想就是利用java的编译命令去编译。

例如

javac -classpath "a.jar;b.jar;c.jar" Test.java -- windows下的写法javac -classpath "a.jar:b.jar:c.jar" Test.java -- Linux下的写法,区别在于分隔符一个是;y一个是:

使用代码实现核心逻辑:

/** * 编译java文件 * @param javaContent 要编译的内容 * @param jarPath 依赖jar包路径 * @param saveClassPath 编译后的.class文件保存路径 */public String compileJava(String javaContent, String jarPath, String saveClassPath, String className) {try {// 创建java编译器实例JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();// 创建对象用于获取编译输出信息DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();// 获得JavaFileManager文件管理器对象,用于管理需要编译的文件StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null);// 生成一个JavaFileObject对象,表示需要编译的源文件GenericJavaFileObject fileObject = new GenericJavaFileObject(className, javaContent);// 获取该工程下所有的jar文件。String importPagePathLogin = getJarFiles(jarPath);// 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。// -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录,-d就是编译文件的输出目录。Map<String, String> cacheMap = Config.getConfig().getsysConfig();String isSave = cacheMap.get("isSave");Iterable<String> options = null;if ("true".equals(isSave)) {options = Arrays.asList("-d", saveClassPath, "-classpath", importPagePathLogin);} else {options =Arrays.asList( "-classpath", importPagePathLogin);}//Iterable<String> options = Arrays.asList("-d", saveClassPath, "-classpath", importPagePathLogin);//Iterable<String> options = Arrays.asList( "-classpath", importPagePathLogin);// 将java文件转化为listIterable<? extends JavaFileObject> fileObjects = Collections.singletonList(fileObject);// 获取编译任务(1、第一个参数为文件输出,这里我们可以不指定,我们采用javac命令的-d参数来指定class文件的生成目录// 2、第二个参数为文件管理器实例// 3、DiagnosticCollector<JavaFileObject> diagnostics是在编译出错时,存放编译错误信息// 4、第四个参数为编译命令选项,就是javac命令的可选项,这里我们主要使用了-d和-sourcepath这两个选项// 5、第五个参数为类名称// 6、第六个参数为上面提到的编译单元,就是我们需要编译的java源文件)JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjects);// 编译Boolean result = task.call();if (result) {System.out.println("编译成功");return "success";} else {System.out.println("编译失败");// 编译失败,打印错误信息StringBuilder errorInfo = new StringBuilder();for (Diagnostic<?> diagnostic : diagnosticCollector.getDiagnostics()) {errorInfo.append("编译错误。 Code:").append(diagnostic.getCode()).append(" ").append("Kind:").append(diagnostic.getKind()).append(" ").append("StartPosition:").append(diagnostic.getStartPosition()).append(" ").append("EndPosition:").append(diagnostic.getEndPosition()).append(" ").append("Position:").append(diagnostic.getPosition()).append(" ").append("Source:").append(diagnostic.getSource()).append(" ").append("Message:").append(diagnostic.getMessage(null)).append(" ").append("ColumnNumber:").append(diagnostic.getColumnNumber()).append(" ").append("LineNumber:").append(diagnostic.getLineNumber());}System.out.println("错误信息如下:" + errorInfo);return errorInfo.toString();}} catch(Exception e) {e.printStackTrace();}return null;}

这样就可以实现动态根据java文件生成class文件,那么再结合初级阶段的热更新方式基本上就可以解决日常开发中常见的问题了。

注意:

动态编译依赖的jar包需要在编译的时候加载进来,我的实现方式是,动态编译不在本地编译而是去测试环境编译,因为测试环境的依赖包肯定是完整的。所以我加载依赖包的路径指定的是容器发布目录下的lib文件夹中的所有文件。

原文:https://juejin.cn/post/7099710892845039647
阅读全文

与基于java下的简单编译器的实现相关的资料

热点内容
编程新建文件packet选什么 浏览:431
如何弱化声音app 浏览:726
华为蓝牙传输文件 浏览:158
什么是手机数据业务 浏览:662
苹果xsmax信任app在哪里设置 浏览:224
如何破手机图案密码 浏览:602
微信支付充10元话费 浏览:288
ipadair2升级需要密码 浏览:873
workflow工具 浏览:148
苹果i7多少钱 浏览:547
文件名不同传送会覆盖吗 浏览:52
毕业设计旅游网站如何做的高端 浏览:677
一般安卓游戏的数据放哪里 浏览:418
vs如何向服务器存取视频文件 浏览:180
关于数据库的翻译好的外文文献 浏览:494
win10设置图标比例 浏览:149
linuxtar命令详解 浏览:774
文件夹不见了怎么找回 浏览:423
linux虚拟机的映像文件 浏览:88
android程序发布 浏览:878

友情链接