1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        我用 Rust 寫了一個 JVM

        共 10028字,需瀏覽 21分鐘

         ·

        2023-08-08 07:56

        最近,我花了相當多的時間來學(xué)習(xí) Rust,就像任何有理智的人都會做的那樣,在編寫了幾個 100 行程序之后,我決定做一些更加雄心勃勃的事情——我用Rust寫了一個 Java 虛擬機。?? 

        我在其中實現(xiàn)了很多獨創(chuàng)特性,我把它稱為『rjvm』。目前代碼已經(jīng)開源,各位可以在 GitHub 上獲取。

        https://github.com/andreabergia/rjvm

        我想強調(diào)的是,這是一個玩具型 JVM,是為了學(xué)習(xí)目的而構(gòu)建的,而不是一個嚴肅的實現(xiàn)。特別是,它不支持:

        • 泛型

        • 線程

        • 反射

        • 注釋

        • 輸入/輸出

        • 及時編譯器

        • 字符串處理


        實際上,我已經(jīng)實現(xiàn)了大多數(shù)重要的事情。比如

        • 控制流語句 ( if, for, ...)

        • 原始和對象創(chuàng)建

        • 虛擬方法和靜態(tài)方法調(diào)用

        • 例外處理

        • 垃圾收集

        • jar文件中的類解析

        例如,以下是測試套件的一部分:

        class StackTracePrinting {    public static void main(String[] args) {        Throwable ex = new Exception();        StackTraceElement[] stackTrace = ex.getStackTrace();        for (StackTraceElement element : stackTrace) {            tempPrint(                    element.getClassName() + "::" + element.getMethodName() + " - " +                            element.getFileName() + ":" + element.getLineNumber());        }    }
        // We use this in place of System.out.println because we don't have real I/O private static native void tempPrint(String value);}

        我使用真實的包,它來自O(shè)penJDK 7 的rt.jar類。所以在上面的示例中,該類來自真實的 JDK!java.lang.StackTraceElement。

        我對自己學(xué)到的關(guān)于 Rust 知識,以及如何實現(xiàn)虛擬機的知識感到非常滿意。

        特別是,我非常興奮能夠?qū)崿F(xiàn)一個真正的、有效的垃圾收集器。它很普通,但它是我親自寫出來的,我很喜歡它。?? 

        鑒于我已經(jīng)實現(xiàn)了最初的目標,我決定停止這個項目。我知道存在一些錯誤,但我并不打算修復(fù)它們。

        概述 


        在這篇文章中,我將向概述 JVM 的工作原理。


        代碼組織 


        該代碼是一個標準的 Rust 項目。我把它分成了三個代碼空間(即包):

        • reader,它能夠讀取.class文件,并包含對其內(nèi)容進行建模以及各種數(shù)據(jù)類型;

        • vm,其中包含可以將代碼作為庫執(zhí)行的虛擬機;

        • vm_cli,其中包含一個非常簡單的命令行啟動器來運行虛擬機,可執(zhí)行java文件。

        我正在考慮將reader提取到單獨的存儲庫中并將其發(fā)布到crates.io上,因為它實際上對其他的開發(fā)者可能有用。

        解析.class文件 


        如大家所知曉的,Java 是一種半編語言 -javac編譯器獲取.java源文件并生成各種.class文件,通常壓縮在一個.jar文件中 - 這是一zip文件因此,執(zhí)行些 Java 代碼要做的第一件事就是加載一個.class文件,其中包含編譯器生成的字節(jié)碼。

        其中,類文件包含如下內(nèi)容:

        • 有關(guān)類的元數(shù)據(jù),例如其名稱或源文件名

        • 超類名稱

        • 實現(xiàn)的接口

        • 字段及其類型與注釋

        • 接下來是方法:

          • 它們的描述符,它是一個字符串,表示每個參數(shù)的類型和方法的返回類型

          • 元數(shù)據(jù),例如throws子句、注釋、泛型信息

          • 字節(jié)碼以及一些額外的元數(shù)據(jù),例如異常處理程序表與行號表。


        就像前面所描述的,我將rjvm創(chuàng)建了一個單獨的盒子,名為reader,它可以解析類文件,并返回一個對類及其所有內(nèi)容進行建模的Rust 結(jié)構(gòu)。

        https://github.com/andreabergia/rjvm/blob/main/reader/src/class_file.rs


        執(zhí)行方法 


        vm包的主要 API是Vm::invoke,用于執(zhí)行方法。

        它需要一個CallStack,其中包含各種CallFrame, 一個用于正在執(zhí)行的每個方法。對于執(zhí)行main,調(diào)用堆棧最初將為空,并且將創(chuàng)建一個新的棧幀來運行它。接下來,每次函數(shù)調(diào)用都會向調(diào)用堆棧添加一個新幀。當方法執(zhí)行完成時,其相應(yīng)的幀將被丟棄,并從調(diào)用堆棧中刪除。

        大多數(shù)方法將用 Java 實現(xiàn),因此它們的字節(jié)碼將被執(zhí)行。但是,rjvm也支持本機方法,即直接由 JVM 實現(xiàn)而不是在 Java 字節(jié)碼中實現(xiàn)的方法。其中相當多的部分位于 Java API 的“較低部分”,需要與操作系統(tǒng)進行交互(例如執(zhí)行 I/O)或支持運行時。

        你可能見過的后者的一些示例包括System::currentTimeMillis、System::arraycopy或Throwable::fillInStackTrace。在 中rjvm,這些是由Rust 函數(shù)實現(xiàn)的。

        JVM是基于堆棧的虛擬機,即字節(jié)碼指令主要在堆棧上操作。還有一組由索引標識的局部變量,可用于存儲值并將參數(shù)傳遞給方法。這些與 中的每個調(diào)用幀相關(guān)聯(lián)rjvm。

        值與對象建模 


        類型Value對局部變量、堆棧元素或?qū)ο笞侄蔚目赡苤颠M行建模,并按如下方式實現(xiàn):

        /// Models a generic value that can be stored in a local variable or on the stack.#[derive(Debug, Default, Clone, PartialEq)]pub enum Value<'a> {    /// An unitialized element. Should never be on the stack,    /// but it is the default state for local variables.    #[default]    Uninitialized,
        /// Models all the 32-or-lower-bits types in the jvm: `boolean`, /// `byte`, `char`, `short`, and `int`. Int(i32),
        /// Models a `long` value. Long(i64),
        /// Models a `float` value. Float(f32),
        /// Models a `double` value. Double(f64),
        /// Models an object value Object(AbstractObject<'a>),
        /// Models a null object Null,}

        順便說一句,這里的 sum 類型(如 Rust 的enum)是一種美妙的抽象——它非常適合表達一個值可能具有多種不同類型的情況。

        為了存儲對象和它的值,我最初實現(xiàn)了一個名為Object 的簡單結(jié)構(gòu),Object其中包含對類的引用(使用對象類型進行建模)和Vec<Value>用來存儲字段值 。

        在我實現(xiàn)垃圾收集器時,我修改了它并以使用較低級別的實現(xiàn),里帶有大量的指針和強制轉(zhuǎn)換 - 相當 C 語言風(fēng)格!

        在當前的實現(xiàn)中,一個 AbstractObject(模擬“真實”對象或數(shù)組)是指向字節(jié)數(shù)組的指針,其中包含幾個標頭字,然后才是字段值。

        執(zhí)行指令 


        執(zhí)行一個方法意味著一次執(zhí)行一個字節(jié)碼指令。


        JVM 有著大量的指令(超過 200 條!),由字節(jié)碼中的一個字節(jié)進行編碼。許多指令后面都有參數(shù),有些指令的長度是可變的。


        這是在代碼中通過Instruction類型建模:

        /// Represents a Java bytecode instruction.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum Instruction {    Aaload,    Aastore,    Aconst_null,    Aload(u8),    // ...


        如上所述,方法的執(zhí)行將保留一個堆棧和一個局部變量數(shù)組,指令通過其索引引用它們。此外,它還會將程序計數(shù)器初始化為零,即下一條要執(zhí)行的指令地址。該指令將被處理并更新程序計數(shù)器 ,通常情況是加 1,但各種跳轉(zhuǎn)指令可以將其移動到不同的位置。

        這些用于實現(xiàn)所有流控制語句,例如if、for或while語言。

        一個特殊的指令系列由那些可以調(diào)用另一種方法的指令組成。

        有多個方法可以解決如應(yīng)該調(diào)用哪個方法的方案。其中虛擬或靜態(tài)查找是主要方法,但還有其它方法。

        當解析完正確的指令后,rjvm將向調(diào)用堆棧添加一個新幀,并立即開始該方法的執(zhí)行。特殊的情況,如果該方法的返回值是void,它將被推送到堆棧,并且將恢復(fù)執(zhí)行。

        Java 字節(jié)碼格式相當有趣,我后面有計劃專門寫一篇文章向大家來介紹各種指令。

        例外與異常處理 


        異常的實現(xiàn)是相當復(fù)雜的,因為它們破壞了正常的控制流,并且可能從方法中提前返回(并在調(diào)用堆棧上傳播)。

        不過,我對實現(xiàn)它們的方式非常滿意,這里向各位展示一些相關(guān)代碼。

        你需要知道的第一件事是,任何catch塊都對應(yīng)于方法異常表的一個條目,每個條目包含程序計數(shù)器范圍、catch 塊中第一條指令的地址以及該塊所處理的異常的類名稱捕獲。

        接下來,CallFrame::execute_instruction的簽名如下:

         fn execute_instruction(    &mut self,    vm: &mut Vm<'a>,    call_stack: &mut CallStack<'a>,    instruction: Instruction,) -> Result<InstructionCompleted<'a>, MethodCallFailed<'a>>

        其中類型為:

        /// Possible execution result of an instructionenum InstructionCompleted<'a> {    /// Indicates that the instruction executed was one of the return family. The caller    /// should stop the method execution and return the value.    ReturnFromMethod(Option<Value<'a>>),
        /// Indicates that the instruction was not a return, and thus the execution should /// resume from the instruction at the program counter. ContinueMethodExecution,}
        /// Models the fact that a method execution has failedpub enum MethodCallFailed<'a> { InternalError(VmError), ExceptionThrown(JavaException<'a>),}

        標準 Rust的Result類型是:

        enum Result<T, E> {   Ok(T),   Err(E),}

        因此,執(zhí)行一條指令會導(dǎo)致四種可能的狀態(tài):

        • 指令執(zhí)行成功,當前方法可以繼續(xù)執(zhí)行(標準情況);

        • 該指令執(zhí)行成功,并且它是一個返回指令,因此當前方法應(yīng)該返回(可選)一個返回值;

        • 該指令無法執(zhí)行,可能發(fā)生了一些內(nèi)部VM錯誤;

        • 或者指令無法執(zhí)行,因為拋出了標準 Java 異常。


        執(zhí)行方法代碼如下:

        /// Executes the whole methodimpl<'a> CallFrame<'a> {    pub fn execute(        &mut self,        vm: &mut Vm<'a>,        call_stack: &mut CallStack<'a>,    ) -> MethodCallResult<'a> {        self.debug_start_execution();
        loop { let executed_instruction_pc = self.pc; let (instruction, new_address) = Instruction::parse( self.code, executed_instruction_pc.0.into_usize_safe() ).map_err(|_| MethodCallFailed::InternalError( VmError::ValidationException) )?; self.debug_print_status(&instruction);
        // Move pc to the next instruction, _before_ executing it, // since we want a "goto" to override this self.pc = ProgramCounter(new_address as u16);
        let instruction_result = self.execute_instruction(vm, call_stack, instruction); match instruction_result { Ok(ReturnFromMethod(return_value)) => return Ok(return_value), Ok(ContinueMethodExecution) => { /* continue the loop */ }
        Err(MethodCallFailed::InternalError(err)) => { return Err(MethodCallFailed::InternalError(err)) }
        Err(MethodCallFailed::ExceptionThrown(exception)) => { let exception_handler = self.find_exception_handler( vm, call_stack, executed_instruction_pc, &exception, ); match exception_handler { Err(err) => return Err(err), Ok(None) => { // Bubble exception up to the caller return Err(MethodCallFailed::ExceptionThrown(exception)); } Ok(Some(catch_handler_pc)) => { // Re-push exception on the stack and continue // execution of this method from the catch handler self.stack.push(Value::Object(exception.0))?; self.pc = catch_handler_pc; } } } } } }}

        我知道這段代碼中有相當多的實現(xiàn)細節(jié),但我希望它能讓大有了解如何使用 Rust的Result和模式匹配很奇妙地映射到上述行為。

        不得不說我對自己寫的這段代碼感到由衷地自豪。??

        垃圾收集 


        rjvm最后的里程碑是垃圾收集器的實現(xiàn)。


        我選擇的算法是一個停止世界。原因很簡單,因為沒有線程!實現(xiàn)半空間復(fù)制收集器。


        我已現(xiàn)尼算法(https://en.wikipedia.org/wiki/Cheney%27s_algorithm的一個(較差的體,但我真的應(yīng)該去實現(xiàn)真正的東西......??


        這個算是將可用內(nèi)存分成兩部分,稱為半空間:一部分將處于活動狀態(tài)并用于分配對象,另一部分將不再使用。


        當空間滿了的時候,將觸發(fā)垃圾收集,所有活動對象將被復(fù)制到另一個半空間。然后,所有對象的引用都將被更新,以便它們被指向新的副本。最后,兩者的角色將互換——類似于藍綠(https://www.redhat.com/en/topics/devops/what-is-blue-green-deployment)部署的工作原理。

        該算法具有以下特點:

        • 很顯然,它浪費了大量內(nèi)存(就是最大內(nèi)存的一半?。?;

        • 分配速度非??欤ㄅ鲎仓羔槪?/span>

        • 復(fù)制和壓縮對象,意味著它不必處理內(nèi)存碎片;

        • 由于更好的緩存行利用率,壓縮對象可以提高性能。

        當然,真正的 Java VM 使用更復(fù)雜的算法,通常是分代垃圾收集器,例如 G1 或并行 GC,它們使用復(fù)制策略的演變版本。

        結(jié)論 


        在寫rjvm的過程中,我學(xué)到了很多,得到了甚多樂趣。當然,不能要求從副業(yè)項目中得到更多......也許下次我會選擇一些不那么雄心勃勃的東西,來學(xué)習(xí)另一門新的編程語言!??


        順便再說一句,我想說從 Rust 中獲得了非常多的樂趣。我認為它是一種很棒的語言,正如我之前寫的那樣,我很喜歡使用它來實現(xiàn)自己的JVM!

        編譯:洛逸

        作者:安德里亞

        https://andreabergia.com/blog/2023/07/i-have-written-a-jvm-in-rust/

        推薦閱讀:

        被 GPT-4 Plus 賬號價格勸退了!

        世界的真實格局分析,地球人類社會底層運行原理

        不是你需要中臺,而是一名合格的架構(gòu)師(附各大廠中臺建設(shè)PPT)

        企業(yè)IT技術(shù)架構(gòu)規(guī)劃方案

        論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

        華為干部與人才發(fā)展手冊(附PPT)

        【中臺實踐】華為大數(shù)據(jù)中臺架構(gòu)分享.pdf

        華為的數(shù)字化轉(zhuǎn)型方法論

        華為如何實施數(shù)字化轉(zhuǎn)型(附PPT)

        華為大數(shù)據(jù)解決方案(PPT)


        瀏覽 162
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            深夜久久AAAAA级毛片免费看 | 31XX.com想要XX | 无码成人日剧在线观看网站 | 黄片视频入口 | 狠狠久久综合 | 男人插女人视频在线观看 | 欧美老妇另类 | 成人高清无码视频在线观看 | 国产精品国产三级国产普通话蜜臀 | 少妇一级淫片中文字幕 |