一個java文件的JVM之旅

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/11/07/17814733.html
-Advertisement-
Play Games

準備 我是小C同學編寫得一個java文件,如何實現我的功能呢?需要去JVM(Java Virtual Machine)這個地方旅行。 變身 我高高興興的來到JVM,想要開始JVM之旅,它確說:“現在的我還不能進去,需要做一次轉換,生成class文件才行”。為什麼這樣呢? JVM不能直接載入java文 ...


準備

我是小C同學編寫得一個java文件,如何實現我的功能呢?需要去JVM(Java Virtual Machine)這個地方旅行。

變身

我高高興興的來到JVM,想要開始JVM之旅,它確說:“現在的我還不能進去,需要做一次轉換,生成class文件才行”。為什麼這樣呢?

JVM不能直接載入java文件的原因:

  • Java源代碼中包含了許多高級語言特性和語法,比如類、繼承、多態、異常處理等等。這些高級特性在JVM中沒有直接對應的形式,只有通過編譯器的處理才能轉化為JVM可以理解的位元組碼指令。
  • Java源代碼需要經過編譯器的編譯過程,才能生成相應的位元組碼文件,然後再由JVM載入、解釋執行。在編譯過程中,編譯器對源代碼進行語法分析、類型檢查、優化等操作,最終生成與目標平臺相容的Java位元組碼文件。
  • JVM只能夠載入和運行符合Java虛擬機規範的.class位元組碼文件,而不能夠直接載入和運行Java源代碼文件。

編譯

知道原因後,我又問JVM,我怎麼才能變成class文件呢,JVM告訴我可以通過javac命令。

javac

javac 是 Java 編譯器命令,用於將 Java 源代碼文件編譯成位元組碼文件(.class 文件)。

命令格式

javac [options] [source files]

  • options:為編譯選項,可以控制編譯器的行為,例如指定類路徑、生成調試信息、壓縮文件等。
  • source files:為需要編譯的 Java 源代碼文件,可以指定多個文件,用空格隔開。如果不指定源代碼文件,則 `javac` 命令會在當前目錄查找所有擴展名為 .java 的文件進行編譯。

需要註意的是,`javac` 命令需要在正確配置 JDK 環境後才能使用。JDK(Java Development Kit)是 Java 開發工具包的縮寫,是 Java 應用程式開發的核心組件之一。

具體實現

編譯器在編譯源文件時,需要對源文件進行語法分析、語義分析和類型檢查等操作。

  • 語法分析:javac命令首先將源文件讀入記憶體,然後進行詞法分析和語法分析。詞法分析器負責將源文件中的字元序列轉換成一個個單詞(Token),然後語法分析器將單片語合成可以被解釋執行的語法結構,形成抽象語法樹(AST)。
  • 語義分析:javac命令在生成AST之後,進行語義分析。語義分析器主要是為了檢查程式中是否存在語義錯誤,例如變數未定義、類型不匹配等,如果發現語義錯誤,編譯器會輸出錯誤信息,並中止編譯過程,不會生成位元組碼文件。
  • 類型檢查:javac命令在語義分析的基礎上,進行類型檢查。類型檢查器主要是檢查程式的類型是否匹配和相容,如果類型不匹配或不相容,編譯器會在編譯期間報告錯誤。
  • 代碼生成:javac命令在生成抽象語法樹後,對其進行優化和轉化,最終生成位元組碼文件。編譯器會根據目標代碼的平臺和版本,生成適當的位元組碼文件。

執行

知道怎麼變身後,我立即通過javac命令,讓自己變成可以被JVM執行的class文件。

載入

變成class文件後,我怎麼能進入JVM內部呢,是走著去還是坐車去呢?JVM告訴我要通過類載入器進入。

類載入器

Java類載入器是Java虛擬機(JVM)中的一個重要組件,它負責將類文件(.class文件)載入到JVM中。

分類

Java 中的類載入器是按照其載入類的特點進行分類的,主要有以下幾種類型:

  • 啟動類載入器(Bootstrap ClassLoader):負責載入 JRE/lib/rt.jar 中的核心 Java 類庫,是最頂層的類載入器,不是 Java 類(因為在 JVM 實現時就已經存在)。
  • 擴展類載入器(Extension ClassLoader):負責載入 JRE/lib/ext 目錄下的擴展類庫,也是由 C++ 實現的類載入器。
  • 應用程式類載入器(APP ClassLoader):負責載入應用程式的類,包括在 CLASSPATH 中指定的類庫或者目錄中的 Java 類。
  • 自定義類載入器(Custom ClassLoader):繼承自 ClassLoader 類,實現自己的類載入器,主要用於載入一些自定義的類或者修改某些類的位元組碼。

查看使用的類載入器

代碼:

public class ClassLoaderTest {
    public static void main(String[] args) {
        //啟動類載入器
        System.out.println(String.class.getClassLoader());
        //擴展類載入器
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
        //應用程式類載入器
        System.out.println(ClassLoaderTest.class.getClassLoader());
        //擴展類載入器的父載入器
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getParent());
        //應用程式類載入器的父載入器
        System.out.println(ClassLoaderTest.class.getClassLoader().getParent());
    }
}


執行結果:

自定義類載入器

自定義類載入器主要包括兩種類型:

  • 獨立的自定義類載入器,通過重載 ClassLoader 類中的 findClass 方法來實現載入類文件的功能;
  • 基於 URLClassLoader 類實現的自定義類載入器,使用 URL 的形式來指定類文件的位置。

重載ClassLoader

代碼:

public class CustomClassLoader extends ClassLoader {
    private String basePath;

    public CustomClassLoader(String basePath) {
        this.basePath = basePath;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = getClassData(name);
        if (data == null) {
            throw new ClassNotFoundException();
        } else {
            // 使用 defineClass 方法將 byte 數組轉換為 Class 對象
            return defineClass(name, data, 0, data.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = basePath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        try (InputStream inputStream = new FileInputStream(path);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            return outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}


說明:

上述代碼繼承了ClassLoader類,並重寫了其中的findClass()方法,實現從指定目錄中載入類文件的功能。

findClass()方法中,首先通過getClassData()方法讀取並返回類文件的位元組數組,如果獲取的位元組數組為空,則拋出ClassNotFoundException異常;否則,使用defineClass()方法將位元組數組轉換為 Class 對象,並返回該對象。

getClassData()方法中,根據傳入的類名生成類文件路徑,並使用FileInputStream將類文件讀入位元組數組中。

使用:

public class CustomClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 創建自定義類載入器,指定類文件所在的目錄
        CustomClassLoader classLoader = new CustomClassLoader("F:\\classes");

        // 使用自定義類載入器載入 Hello 類
        Class<?> clazz = classLoader.loadClass("com.example.something.Hello");
        Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj);
    }
}


基於 URLClassLoader

代碼:

public class CustomURLClassLoader extends URLClassLoader {
    public CustomURLClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 調用父類 loadClass 方法進行委托載入
            Class<?> clazz = super.findClass(name);
            return clazz;
        } catch (ClassNotFoundException e) {
            // 如果父類無法載入,則嘗試在 URL 中載入
            byte[] data = getClassData(name);
            if (data == null) {
                throw new ClassNotFoundException();
            } else {
                // 使用 defineClass 方法將 byte 數組轉換為 Class 對象
                return defineClass(name, data, 0, data.length);
            }
        }
    }

    private byte[] getClassData(String className) {
        String path = className.replace('.', '/') + ".class";
        URL[] urls = getURLs();
        for (URL url : urls) {
            try {
                URL classUrl = new URL(url, path);
                // 使用 URLConnection 檢查類文件是否存在
                try (InputStream is = classUrl.openStream();
                     ByteArrayOutputStream os = new ByteArrayOutputStream()) {
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = is.read(buffer)) != -1) {
                        os.write(buffer, 0, length);
                    }
                    return os.toByteArray();
                }
            } catch (IOException e) {
                // ignore and try next URL
            }
        }
        return null;
    }
}


說明:

上述代碼繼承了 `URLClassLoader` 類,並重寫了其中的 `findClass()` 方法,實現先嘗試使用父類載入器進行載入,如果無法載入,則嘗試使用 URL 載入類文件的功能。在 `getClassData()` 方法中,會遍歷 `URLClassLoader` 中定義的 URL,檢查類文件是否存在,並返回類文件的位元組數組,如果無法找到類文件,則返回 `null`。

使用:

public class CustomURLClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 創建 URL 數組,指定類文件所在的 URL
        URL[] urls = { new URL("file:F:\\classes") };

        // 創建父類載入器,使用系統類載入器
        ClassLoader parent = ClassLoader.getSystemClassLoader();

        // 創建自定義 URL 類載入器
        CustomURLClassLoader classLoader = new CustomURLClassLoader(urls, parent);

        // 使用自定義 URL 類載入器載入 Hello 類
        Class<?> clazz = classLoader.loadClass("com.example.something.Hello");
        Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj);
    }
}


雙親委派

載入器那麼多,我具體是哪個類進行載入得呢?雙親委派機制告訴我答案.

定義

雙親委派是一種Java類載入器的工作機制,它將類載入請求委派給父類載入器,直到頂級系統類載入器。基本思想是,除非有特殊需求,否則所有類的載入任務都應該由父類載入器完成,從而保證Java核心庫的類型安全和穩定性,並防止惡意代碼的自行佈置。如果一個類沒有在父類載入器中被髮現,子類載入器才會嘗試載入該類。這種類載入器之間的父子關係被稱為“雙親委派模型”.

如圖:

意義

為什麼通過雙親委派進行載入呢?

  • 避免重覆載入
  • 提高安全性
  • 維護Java平臺的一致性
  • 代碼優化

Linking

載入過後,我是否就可以被使用了呢?答案是否定的,我還要經歷Lingking 階段,包括Verification、Preparation 和 Resolution。

Verification(驗證)

在驗證階段,Java虛擬機會進行語法與語義的檢查,以保證class文件的完整性和正確性,同時保證被載入的class與虛擬機的版本相容。主要的檢查內容包括文件格式、位元組碼語義、符號引用等。

Preparation(準備)

在準備階段,Java虛擬機會為類變數分配記憶體,並且賦予初始值。如果類變數包含有靜態變數,那麼這時也會初始化靜態變數。因此,在這個階段,類變數所使用的空間已經被分配,將其設置為預設初始值即可。

Resolution(解析)

在解析階段,將類或介面中的符號引用轉化為直接引用的過程。在 Java 虛擬機載入類時,符號引用是一種指向常量池中某個符號的引用,而直接引用則是指向記憶體中某個位置的直接指針。解析階段可以理解為是在解決類之間的依賴關係,使各個類之間可以像使用自身成員一樣使用別的類中的成員。

初始化

在驗證、準備和解析後,我還要經過初始化,才能被使用。

定義

初始化是指在類載入過程的最後一步,JVM要對類進行一些初始化的操作,確保類可以安全地使用。在這個階段,往往包括靜態變數顯式賦值和靜態代碼塊執行。

內容

靜態變數顯式賦值

當類載入器完成類的載入、驗證、準備後,在初始化階段,JVM對類的靜態變數進行顯式賦值。如果類定義了多個靜態變數,JVM會按照代碼中聲明的順序進行初始化,並且若發現此過程需要訪問到其他未初始化的類,JVM會先完成這些類的初始化。

靜態代碼塊的執行

除了靜態變數的顯式賦值,類的靜態代碼塊也會在初始化階段執行。當JVM執行類載入的Initializing階段時,會執行類中所有靜態代碼塊的內容,如果類中沒有定義靜態代碼塊,則不執行。這個過程一般用於在使用之前對類進行初始化。

介面初始化

當一個類在初始化時,如果發現其父類還未進行初始化,JVM會先對其父類進行初始化。如果該類實現了介面,也會對這個介面進行初始化操作,介面的初始化過程和類一樣,都會進行靜態變數顯式賦值及靜態代碼塊執行,同時還會檢查介面中的所有靜態方法。

功能實現

初始化之後,我才真正的進入JVM中,其它小伙伴需要我的時候,只需要創建我的實例,就可以使用我的功能了,得到我幫助得小伙伴都很感謝我。

GC

在JVM中我過得很開心,也留下了很多足跡。在我走後,如何讓我得足跡不對其他小伙伴有影響呢?GC可以幫我解決這個問題。

定義

GC(Garbage Collection)是JVM提供的垃圾回收機制。在Java中,對象是動態分配的,記憶體是由JVM自動管理,而不是由程式員手動分配和釋放。當一個對象不再被程式引用時,就應該由垃圾回收器回收其占用的記憶體,這樣可以防止記憶體泄漏和提高記憶體的。

小結

通過我的旅行,你知道JVM是怎麼載入一個類的了麽?我們通過載入、Linking、初始化和使用等各個階段,將Java類完整地載入記憶體並執行其中定義的方法和變數。這個過程中,每個階段都扮演著不同的角色,併為類的正常運行提供了必要的支持。

作者:京東物流 陳昌浩

來源:京東雲開發者社區 自猿其說Tech 轉載請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 機器學習是通過研究數據和統計信息使電腦學習的過程。機器學習是邁向人工智慧(AI)的一步。機器學習是一個分析數據並學會預測結果的程式。 數據集 在電腦的思維中,數據集是任何數據的集合。它可以是從數組到完整資料庫的任何東西。 數組的示例: [99,86,87,88,111,86,103,87,94, ...
  • @目錄山茶花100ptsT1區間逆序對60pts100pts 區間操作固定套路,轉化為首碼操作dream20pts 神奇分塊杭州:轉化題意,正難則反正難則反(或者對於這種有刪邊操作的題), 我們看成反向加邊看題:構造坐飛機:斜率優化DP抓頹 : 啟髮式合併 + stl大雜燴討厭的線段樹Foo Fig ...
  • Spring5框架概述 Spring是輕量級的開源的JavaEE框架。 Spring可以解決企業應用開發的複雜性。 Spring有兩個核心部分:IOC和AOP IOC:控制反轉,把創建對象過程交給Spring進行管理 AOP:面向切麵,不修改源代碼進行功能增強 Spring特點 方便解耦,簡化開發( ...
  • 1. 左值、右值、左值引用以及右值引用 左值:一般指的是在記憶體中有對應的存儲單元的值,最常見的就是程式中創建的變數 右值:和左值相反,一般指的是沒有對應存儲單元的值(寄存器中的立即數,中間結果等),例如一個常量,或者表達式計算的臨時變數 int x = 10 int y = 20 int z = x ...
  • eclipse下載 官網下載:https://www.eclipse.org/downloads/packages/ 打開後,找到Eclipse IDE for Java Developers點擊進入 進入後點擊右側電腦適配的版本,進入到下載界面點擊“>> Select Another Mirror ...
  • 1 任務調度整體流程 2 組件 調度器 :工廠類創建Scheduler,根據觸發器定義的時間規則調度任務 任務:Job表示被調度的任務 觸發器:Trigger 定義調度時間的元素,按啥時間規則執行任務。一個Job可被多個Trigger關聯,但是一個Trigger 只能關聯一個Job import o ...
  • 進行支付寶開發的第一步就是:配置密鑰。 但是有很多小伙伴都不知道怎麼配置,這篇文章將手把手幫你從頭開始捋清如何配置支付寶密鑰~ ...
  • 在"zookeeper源碼(03)集群啟動流程"中介紹了leader選舉的入口,本文將詳細分析leader選舉組件和流程。 leader選舉流程(重要) quorumPeer的start階段使用startLeaderElection()方法啟動選舉 LOOKING狀態,投自己一票 createEle ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...