導航:首頁 > 編程語言 > 基於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下的簡單編譯器的實現相關的資料

熱點內容
java局部變數和成員變數默認值 瀏覽:332
廣數內錐螺紋怎麼編程 瀏覽:223
u盤里的病毒文件怎麼出現在c盤 瀏覽:831
一般安卓手機數據線多少厘米 瀏覽:76
網站如何存儲資料庫 瀏覽:666
四個店鋪的監控網路掉線怎麼操作 瀏覽:223
什麼軟體可以瀏覽所有子文件夾 瀏覽:63
如何啟用網路適配器卡 瀏覽:748
無法打開包括文件stdinth 瀏覽:743
淘寶c店頂部關聯代碼 瀏覽:344
編程新建文件packet選什麼 瀏覽:431
如何弱化聲音app 瀏覽:726
華為藍牙傳輸文件 瀏覽:158
什麼是手機數據業務 瀏覽:662
蘋果xsmax信任app在哪裡設置 瀏覽:224
如何破手機圖案密碼 瀏覽:602
微信支付充10元話費 瀏覽:288
ipadair2升級需要密碼 瀏覽:873
workflow工具 瀏覽:148
蘋果i7多少錢 瀏覽:547

友情鏈接