Java記憶體區域與虛擬機類載入機制

来源:https://www.cnblogs.com/gdwkong/archive/2018/02/17/8450310.html
-Advertisement-
Play Games

本文主要對Java運行時數據區域、對象的創建過程、對象的記憶體佈局、對象的訪問定位、虛擬機類的載入機制進行簡單的介紹梳理。 ...


一、Java運行時數據區域 

1、程式計數器

  “線程私有”的記憶體,是一個較小的記憶體空間,它可以看做當前線程所執行的位元組碼的行號指示器。Java虛擬機規範中唯一一個沒有OutOfMemoryError情況的區域。

  位元組碼解釋器工作時就說通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

2、Java虛擬機棧

  Java 虛擬棧,線程私有的,它的生命周期與線程相同。每個方法在執行的同時都會創建一個棧幀用於存儲局部變數表、操作數棧、動態鏈接、方法出口等信息。

  通過人們所說的“棧”就說虛擬機棧,或說是虛擬機棧中的局部變數表部分。

  • 局部變數表存放了編譯期可知的各種基本數據類型(bloolean,byte,char,short,int,float,long,double),對象引用(reference類型,它不等同於對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向了一條位元組碼指令的地址)。局部變數表所需的記憶體空間在編譯器間完成分配,當進入一個方法是,這個方法需要在幀中分配多大的局部變數空間是完全確定的,在方法運行期間不會改變局部變數表的大小。
  • 一個後進先出(Last-In-First-Out)的操作數棧,也可以稱之為表達式棧(Expression Stack)。操作數棧和局部變數表在訪問方式上存在著較大差異,操作數棧並非採用訪問索引的方式來進行數據訪問的,而是通過標準的入棧和出棧操作來完成一次數據訪問。每一個操作數棧都會擁有一個明確的棧深度用於存儲數值,一個32bit的數值可以用一個單位的棧深度來存儲,而2個單位的棧深度則可以保存一個64bit的數值,當然操作數棧所需的容量大小在編譯期就可以被完全確定下來,並保存在方法的Code屬性中。  

  這個區域有兩個異常:
     ① 如果線程請求的棧深度大於虛擬所允許的深度,將拋StackOverflowError異常;
     ② 虛擬機棧可以動態擴展,但擴展時無法申請到足夠的記憶體,就會拋出OutOfMemoryError異常。

3、本地方法棧

  本地方法棧作用與虛擬機棧相似,區別在於虛擬機棧為虛擬機執行java方法(也就是位元組碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務,線程私有
  Native方法常用的兩種請求:
    ① 在方法中調用一些不是有java語言寫的代碼;
    ② 在方法中用java語言直接操作電腦硬體;
  異常:StackOverflowError、OutOfMemoryError

4、Java堆(Java Heap)

  Java堆是Java虛擬機所管理的記憶體中最大的一塊。在虛擬機啟動時創建,此記憶體區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裡分配記憶體。
  Java堆是垃圾收集器管理的主要區域,因此也稱為“GC堆”;
  如果在堆中沒有記憶體完成實例分配,並且堆也無法擴展時,將會拋出OutOfMemoryError異常。

5、方法區

  方法區用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯器編譯後的代碼等數據。
  當方法區無法滿足記憶體分配需求時,將拋出OutOfMemoryError異常。

  • 運行時常量池是方法區的一部分。Class文件中除了有類的版本、欄位、方法、介面等描述信息外,還有一項信息是常量池,用於存儲編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的運行時常量池中存放。
  • 運行時常量池相對於Class文件常量池的另外一個特征是具備動態性,Java語言並不要求常量一定只有編譯期才能產生,也就是並非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用的比較多的便是String類的intern()方法。

 二、對象的創建

  1、虛擬機遇到一條new指令時,首先檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入,解析和初始化過,如果沒有則必須執行相應的類載入過程。

  2、在類載入檢查通過後,接下來虛擬機將新生對象分配記憶體。對象所需記憶體的大小在類載入完成後便可完全確定,為對象分配空間的任務等同於把一塊確定大小的記憶體從Java堆中划出來。

  3、記憶體分配完成後,虛擬機需要將分配到的記憶體空間都初始化為零值(不包括對象頭)。

  4、虛擬機要對對象進行必要的設置,主要針對對象頭的設置。

    • 對象頭:
      • 第一部分,用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。(Mark Word)  
      • 第二部分,類性指針,即對象指向他的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

  5、從虛擬機的角度來看,一個新的對象已經產生了,但從Java查詢的視角來看,對象創建才剛剛開始—— <init>方法還沒執行,所有的欄位都還為零。

     執行完new指令之後會接著執行<init>方法,把對象按照程式員的意願進行初始化。這樣一個真正的對象才算完全產生出來。

三、對象的記憶體佈局

  對象在記憶體中存儲的佈局可以分為3塊局域:對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding)。

  實例數據:對象真正存儲的有效信息,也是在程式代碼中所定義的各種類型的欄位內容。

  對齊填充:並不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。

四、對象的訪問定位

  建立對象是為了使用對象,我們的Java程式需要通過棧上的reference數據來操作堆上的具體對象。目前主流的訪問方式有使用句柄和直接指針兩種。

  1、如果使用句柄訪問的話,那麼Java堆中將會划出一塊記憶體來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型各自的具體地址信息。

  2、如果通過直接指針訪問,那麼Java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象的地址。(Sun HotSpot的實現方式)

 

五、虛擬機類的載入機制

  (一)、類載入的時機

  1、類的生命周期:類從被載入到虛擬機記憶體中開始,到卸載出記憶體為止。

 

  圖中,載入、驗證、準備、初始化和卸載這五個階段的順序是確定的,類的載入過程必須按照這種順序按部就班的開始,而解析階段則不一定。

  2、需立即對類進行“初始化”(而載入、驗證、準備自然需要在此之前開始)的有且只有的5種請求:

    ① 遇到new、gerstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。

    如:使用new關鍵字實例化對象時候,讀取或設置一個類的靜態欄位(被final修飾,已在編譯期把結果放入常量池的靜態欄位除外)的時候,以及調用一個類的靜態方法的時候。

    ②使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。

    ③當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。

    ④當虛擬機啟動時,用戶需要指定一個要執行的主類(包括main()方法的那個類),虛擬機會先初始化這個主流。

    ⑤當使用JDK1.7的動態語言支持時。

  (二)、類載入的過程

  類載入的全過程:載入、驗證、準備、解析和初始化這五個階段。  

  1、載入

  “載入”是“類載入”過程的一個階段。 

   ① 通過一個類的全限定名來獲取定義此類的二進位位元組流(通過類載入實現);

     ② 將這個位元組流所代表的靜態存儲結構轉化為方法區的運行時數據結構;

   ③ 在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。

   2、驗證

  連接階段的第一步,這一階段的目的是為了確保Class文件的位元組流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。

  大致完成4個階段的檢驗動作:文件格式驗證、元數據驗證、位元組碼驗證、符號引用驗證。

  3、準備

  正式為變數分配記憶體並設置類變數初始值得階段,這些變數所使用的記憶體都在方法區中進行分配。首先這個時候進行記憶體分配的僅包括類變數(被static修飾的變數),而不包括實例變數,實例變數將會在對象實例化時隨著對象一起分配在Java堆中,其次,這裡所說的“初始值”通常情況下是數據類型的零值。

  4、解析

  解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。

    • 符號引用:符號應用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時無歧義地定位到目標即可,與虛擬機實現的記憶體佈局無關,引用的目標並不一定以及載入到記憶體中。
    • 直接引用:直接引用可以直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的記憶體佈局相關的。引用的目標必定已存在於記憶體中。

  在16個用於操作符號引用的位元組碼指令之前,先對它們所使用的符號引用進行解析。所有虛擬機實現可以根據需要來判斷到底是在類被載入器載入時就對常量池中的符號引用進行解析,還是等到一個符號引用將要被使用之前才去解析它。

  解析動作主要針對類或介面、欄位、類方法、方法類型、方法句柄和調用點限定符7類符號引用進行解析。

  5、初始化  

  初始化階段是類載入過程的最後一步,前面的類載入過程中,除了在載入階段用戶應用程式可以通過自定義類載入器參與之外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程式代碼(或者說是位元組碼)。

  在準備階段,變數已經賦過一次系統要求的初始值,而在初始階段,則根據查詢員通過查詢制定的主觀計划去初始化變數和其他資源,換而言之,初始化階段是執行類類構造器<client>()方法的過程。

  在<client>()方法中,靜態語句塊中只能訪問到定義在靜態語句塊之前的變數,定義在他之後的變數,在前面的靜態語句塊可以賦值,但不能訪問。

六、類載入器

  類載入器是類載入過程中載入階段中“通過一個類的全限定名來獲取描述此類的二載入位元組流”的載入動作。

  主要分為啟動類載入器(C++語言實現,是虛擬機自身的一部分)、擴展類載入器、應用程式類載入器,後面兩類載入器由Java語言實現,獨立於虛擬機外部,並全部繼承自抽象類java.lang.Loader。

  其載入順序的實現為雙親委托派模型,如下圖所示:

 


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

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 資料庫類別 首先選擇一個關係資料庫。目前廣泛使用的關係資料庫也就這麼幾種: 付費的商用資料庫: Oracle:典型的高富帥; SQL Server:微軟自家產品,Windows定製專款; DB2:IBM的產品,聽起來挺高端; Sybase:曾經跟微軟是好基友,後來關係破裂,現在家境慘淡。 ...
  • 什麼是JDBC JDBC全稱為:Java Data Base Connectivity,它是可以執行SQL語句的Java API 為什麼我們要用JDBC 市面上有非常多的資料庫,本來我們是需要根據不同的資料庫學習不同的API,sun公司為了簡化這個操作,定義了JDBC API【介面】 sun公司只是 ...
  • .NET Core的支持文檔大體上可以參考文檔.Net客戶端使用指南:https://github.com/ctripcorp/apollo/wiki/.Net%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97登錄Apoll... ...
  • 結合之前的 "串口實驗(輪詢方式)" 與 "中斷體系分析" ,我們來做下中斷方式的串口接收實驗。 start.S ~~~~ .global _start .global IRQ_handle _start: / 關 Watch Dog / ldr r0, =0xE2700000 mov r1, 0 ...
  • 要說到為什麼會有這一次網路配置的問題,還要從配置DNF(游戲登錄器)開始說,由於Linux伺服器是用的別人架設好的端,外網訪問又需要配置PHP網關訪問頁面, 無奈電腦是個人電腦,處於電信分配的大型內網中,沒辦法只能用nat123穿透內網,糾結的是80埠又不好申請,就修改了Linux的Apache ...
  • S5PV210 UART 相關說明 通用非同步收發器簡稱 UART, 即 UNIVERSAL ASYNCHRONOUS RECEIVER AND TRANSMITTER,它用來傳輸串列數據。發送數據時, CPU 將並行數據寫入 UART,UART 按照一定的格式在一根電線上串列發出;接收數據時, UA ...
  • 自14年開始從事資料庫實施,至今(2018-02-16)晃眼間已經四個年頭過去了,工作上的能力要求多數能自己解決,可這不應該成為我學習路上的終點。加之總覺得自己對sql 的理解有些浮於錶面,所以藉著春節放假的時間到此取經。人常言:獨學而無友,則孤陋而寡聞,最近幾天發現有幾個博友的sql 文章寫得非常 ...
  • 一、前言 接著上一章的內容,繼續本章的學習。本章知識來自於https://www.cnblogs.com/jach/p/5709175.html 二、內容 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...