清蒸 JVM (一)

jvm.png

前言

 JVM(Java Virtual Machine)Java 虛擬機(jī)是整個 java 平臺的基石,是 java 系統(tǒng)實現(xiàn)硬件無關(guān)與操作系統(tǒng)無關(guān)的關(guān)鍵部分,是保障用戶機(jī)器免于惡意代碼損害的屏障。Java開發(fā)人員不需要了解JVM是如何工作的,**但是,**了解 JVM 有助于我們更好的開(通)發(fā)(過) java(公司) 程(面)序(試)。

寫這篇文章的目的:

  • 總結(jié)所學(xué)的 JVM 知識
  • 幫助想了解 JVM 的朋友,知無不言,言無不盡

本篇文章將會介紹一下內(nèi)容:

什么是 JVM

要想說明白什么 JVM 就不得不提另外兩個概念,JRE 和 JDK,初學(xué)者總是把這幾個概念搞混
java-tutorial.png

Jvm,Jre,Jdk 都是 java 語言的支柱,他們分工協(xié)作。但不同的是 Jdk 和 Jre 是真實存在的,而 Jvm 是一個抽象的概念,并不真實存在。

JDK
JDK(Java Development Kit) 是 Java 語言的軟件開發(fā)工具包(SDK)。JDK 物理存在,是 programming tools、JRE 和 JVM 的一個集合

jdk.png

JRE
JRE(Java Runtime Environment)Java 運(yùn)行時環(huán)境,JRE 物理存在,主要由Java API 和 JVM 組成,提供了用于執(zhí)行 java 應(yīng)用程序最低要求的環(huán)境。
jre.png

JVM
JVM(Java Virtual Machine) 是一種軟件實現(xiàn),執(zhí)行像物理機(jī)程序的機(jī)器(即電腦)。
本來,Java被設(shè)計基于從物理機(jī)器分離實現(xiàn)WORA( 寫一次,隨處運(yùn)行 )的虛擬機(jī)上運(yùn)行,雖然這個目標(biāo)已經(jīng)幾乎被遺忘。
JVM 并不是專為 Java 所實現(xiàn)的運(yùn)行時,實際上只要有其他編程語言的編譯器能生成正確 Java bytecode 文件,則這個語言也能實現(xiàn)在JVM上運(yùn)行。
因此,JVM 通過執(zhí)行 Java bytecode 可以使 java 代碼在不改變的情況下運(yùn)行在各種硬件之上。
jVM 有如下特點:

  • 基于堆棧的虛擬機(jī) :最流行的計算機(jī)體系結(jié)構(gòu),如英特爾X86架構(gòu)和ARM架構(gòu)上運(yùn)行基于寄存器 。 但是,JVM是基于棧的。
  • 符號引用 :除了基本類型以外的數(shù)據(jù)(類和接口)都是通過符號來引用,而不是通過顯式地使用內(nèi)存地址來引用。
  • 垃圾收集 :一個類的實例是由用戶明確創(chuàng)建的代碼和垃圾回收自動銷毀。
    通過明確界定的基本數(shù)據(jù)類型的保證平臺的獨立性 :傳統(tǒng)的語言,如C / C ++根據(jù)平臺有不同的int型的大小。 JVM中明確規(guī)定了基本數(shù)據(jù)類型,以保持它的兼容性和保證平臺的獨立性。
  • 網(wǎng)絡(luò)字節(jié)順序 :Java class文件用網(wǎng)絡(luò)字節(jié)碼順序來進(jìn)行存儲:為了保證和小端的Intel x86架構(gòu)以及大端的RISC系列的架構(gòu)保持無關(guān)性,JVM使用用于網(wǎng)絡(luò)傳輸?shù)木W(wǎng)絡(luò)字節(jié)順序,也就是大端。

**Java bytecode **
為了實現(xiàn)WORA,JVM使用Java字節(jié)碼,java(用戶語言)和機(jī)器語言之間的中間語言。
該Java字節(jié)碼是部署Java代碼的最小單位。

JVM 用來做什么

基于安全方面考慮,JVM 要求在 class 文件中使用許多強(qiáng)制性的語法和機(jī)構(gòu)化約束,但任意一門功能性語言都可以表示為一個能被 JVM 接受的有效的 class 文件。作為一個通用的、機(jī)器無關(guān)的執(zhí)行平臺,任何其他語言的實現(xiàn)者都可將 JVM 當(dāng)作他的語言產(chǎn)品交付媒介。

JVM 中執(zhí)行以下操作:

  • 加載代碼
  • 驗證代碼
  • 執(zhí)行代碼
  • 提供運(yùn)行環(huán)境

JVM 提供定義了:

  • 存儲區(qū)
  • 類文件格式
  • 寄存器組
  • 垃圾回收堆
  • 致命錯誤報告等

JVM 生命周期

  • 啟動:任何一個擁有main函數(shù)的class都可以作為JVM實例運(yùn)行的起點
  • 運(yùn)行:main函數(shù)為起點,程序中的其他線程均有它啟動,包括daemon守護(hù)線程和non-daemon普通線程。daemon是JVM自己使用的線程比如GC線程,main方法的初始線程是non-daemon。
  • 消亡:所有線程終止時,JVM實例結(jié)束生命。

JVM 的整體架構(gòu)

先看一下 java 代碼執(zhí)行過程
jvm.png

疑問:

  • Class Loader
  • Excution Engine
  • Runtime Data Areas

Class Loader

類加載器負(fù)責(zé)加載程序中的類型(類和接口),并賦予唯一的名字。

JDK 默認(rèn)提供了三種 ClassLoader

classloader.png

關(guān)系

  1. Bootstrp loader 是在Java虛擬機(jī)啟動后初始化的。
  • Bootstrp loader 負(fù)責(zé)加載 ExtClassLoader,并且將 ExtClassLoade r的父加載器設(shè)置為 Bootstrp loader。
  • Bootstrp loader 加載完 ExtClassLoader 后,就會加載 AppClassLoader,并且將 AppClassLoader 的父加載器指定為 ExtClassLoader。
Class Loader 實現(xiàn) 負(fù)責(zé)加載
Bootstrp loader C++ %JAVA_HOME%/jre/lib,-Xbootclasspath參數(shù)指定的路徑以及%JAVA_HOME%/jre/classes中的類
ExtClassLoader Java %JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統(tǒng)變量指定的路徑中類庫
AppClassLoader Java classpath所指定的位置的類或者是jar文檔,它也是Java程序默認(rèn)的類加載器

雙親委托模型
Java中ClassLoader的加載采用了雙親委托機(jī)制,采用雙親委托機(jī)制加載類的時候采用如下的幾個步驟:

  1. 當(dāng)前ClassLoader首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類。
  2. 當(dāng)前classLoader的緩存中沒有找到被加載的類的時候,委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,一直到bootstrp ClassLoader.
  3. 當(dāng)所有的父類加載器都沒有加載的時候,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

為什么使用雙親委托模型——ClassLoader 隔離問題
每個類裝載器都有一個自己的命名空間用來保存已裝載的類。當(dāng)一個類裝載器裝載一個類時,它會通過保存在命名空間里的類全局限定名(Fully Qualified Class Name)進(jìn)行搜索來檢測這個類是否已經(jīng)被加載了。
大家覺得一個運(yùn)行程序中有沒有可能同時存在兩個包名和類名完全一致的類?
JVM 及 Dalvik 對類唯一的識別是 ClassLoader id + PackageName + ClassName,所以一個運(yùn)行程序中是有可能存在兩個包名和類名完全一致的類的。并且如果這兩個”類”不是由一個 ClassLoader 加載,是無法將一個類的示例強(qiáng)轉(zhuǎn)為另外一個類的,這就是 ClassLoader 隔離。
雙親委托是 ClassLoader 問題的一種解決方案,也是 Android 差價化開發(fā)和熱修復(fù)的基礎(chǔ)。

Android 插件化 動態(tài)升級
Android 熱補(bǔ)丁動態(tài)修復(fù)框架小結(jié)

類裝載器特點
Java提供了動態(tài)加載特性;他會在運(yùn)行時的第一次引用到一個class的時候?qū)λM(jìn)行裝載(Loading)、鏈接(Linking)和初始化(Initialization),而不是在編譯時進(jìn)行。不同的JVM的實現(xiàn)不同,本文所描述的內(nèi)容均只限于Hotspot Jvm。JVM的類裝載器負(fù)責(zé)動態(tài)裝載,Java的類裝載器有如下幾個特點:

  • 層級結(jié)構(gòu):Java里的類裝載器被組織成了有父子關(guān)系的層級結(jié)構(gòu)。Bootstrap類裝載器是所有裝載器的父親。
  • 代理模式: 基于層級結(jié)構(gòu),類的代理可以在裝載器之間進(jìn)行代理。當(dāng)裝載器裝載一個類時,首先會檢查它在父裝載器中是否進(jìn)行了裝載。如果上層裝載器已經(jīng)裝載了這個類,這個類會被直接使用。反之,類裝載器會請求裝載這個類
  • 可見性限制:一個子裝載器可以查找父裝載器中的類,但是一個父裝載器不能查找子裝載器里的類。
  • 不允許卸載:類裝載器可以裝載一個類但是不可以卸載它,不過可以刪除當(dāng)前的類裝載器,然后創(chuàng)建一個新的類裝載器裝載。

過程

加載(Loading)是這樣一個過程,找到代表這個類的class文件或根據(jù)特定的名字找到接口類型,然后讀取到一個字節(jié)數(shù)組中。接著,這些字節(jié)會被解析檢驗它們是否代表一個Class對象并包含正確的major、minor版本信息。直接父類的類和接口也會被加載進(jìn)來。這些操作一旦完成,類或者接口對象就從二進(jìn)制表示中創(chuàng)建出來了。

鏈接(Linking)是檢驗類或接口并準(zhǔn)備類型和父類接口的過程。鏈接過程包含三步:校驗(Verifying)、準(zhǔn)備(Preparing)、部分解析(Optionally resolving)。

loadclass.png
  • 驗證:這是類裝載中最復(fù)雜的過程,并且花費的時間也是最長的。任務(wù)是確保導(dǎo)入類型的準(zhǔn)確性,驗證階段做的檢查,運(yùn)行時不需要再做,雖然減慢加了載速度,但是避免了多次檢查。
  • 準(zhǔn)備:分配一個結(jié)構(gòu)用來存儲類信息,這個結(jié)構(gòu)中包含了類中定義的成員變量,方法和接口的信息。
  • 解析:可選階段,把這個類的常量池中的所有的符號引用改變成直接引用。如果不執(zhí)行,符號解析要等到字節(jié)碼指令使用這個引用時才會進(jìn)行

初始化(Initialization)把類中的變量初始化成合適的值。執(zhí)行靜態(tài)初始化程序,把靜態(tài)變量初始化成指定的值。

JVM規(guī)范定義了上面的幾個任務(wù),不過它允許具體執(zhí)行的時候能夠有些靈活的變動。

執(zhí)行引擎(Execution Engine)

通過類裝載器裝載的,被分配到JVM的運(yùn)行時數(shù)據(jù)區(qū)的字節(jié)碼會被執(zhí)行引擎執(zhí)行。執(zhí)行引擎以指令為單位讀取 Java 字節(jié)碼。它就像一個 CPU 一樣,一條一條地執(zhí)行機(jī)器指令。每個字節(jié)碼指令都由一個1字節(jié)的操作碼和附加的操作數(shù)組成。執(zhí)行引擎取得一個操作碼,然后根據(jù)操作數(shù)來執(zhí)行任務(wù),完成后就繼續(xù)執(zhí)行下一條操作碼。
不過 Java 字節(jié)碼是用一種人類可以讀懂的語言編寫的,而不是用機(jī)器可以直接執(zhí)行的語言。因此,執(zhí)行引擎必須把字節(jié)碼轉(zhuǎn)換成可以直接被 JVM 執(zhí)行的語言。字節(jié)碼可以通過以下兩種方式轉(zhuǎn)換成合適的語言。
  • 解釋器:一條一條地讀取,解釋并且執(zhí)行字節(jié)碼指令。因為它一條一條地解釋和執(zhí)行指令,所以它可以很快地解釋字節(jié)碼,但是執(zhí)行起來會比較慢。這是解釋執(zhí)行的語言的一個缺點。字節(jié)碼這種“語言”基本來說是解釋執(zhí)行的。

  • 即時(Just-In-Time)編譯器:即時編譯器被引入用來彌補(bǔ)解釋器的缺點。執(zhí)行引擎首先按照解釋執(zhí)行的方式來執(zhí)行,然后在合適的時候,即時編譯器把整段字節(jié)碼編譯成本地代碼。然后,執(zhí)行引擎就沒有必要再去解釋執(zhí)行方法了,它可以直接通過本地代碼去執(zhí)行它。執(zhí)行本地代碼比一條一條進(jìn)行解釋執(zhí)行的速度快很多。編譯后的代碼可以執(zhí)行的很快,因為本地代碼是保存在緩存里的。

    Java 字節(jié)碼是解釋執(zhí)行的,但是沒有直接在 JVM 宿主執(zhí)行原生代碼快。為了提高性能,Oracle Hotspot 虛擬機(jī)會找到執(zhí)行最頻繁的字節(jié)碼片段并把它們編譯成原生機(jī)器碼。編譯出的原生機(jī)器碼被存儲在非堆內(nèi)存的代碼緩存中。通過這種方法(JIT),Hotspot 虛擬機(jī)將權(quán)衡下面兩種時間消耗:將字節(jié)碼編譯成本地代碼需要的額外時間和解釋執(zhí)行字節(jié)碼消耗更多的時間。

java_compiler_and_jit_compiler.png

這里插入一下 Android 5.0 以后用的 ART 虛擬機(jī)使用的是 AOT 機(jī)制。

Dalvik 是依靠一個 Just-In-Time (JIT)編譯器去解釋字節(jié)碼。開發(fā)者編譯后的應(yīng)用代碼需要通過一個解釋器在用戶的設(shè)備上運(yùn)行,這一機(jī)制并不高效,但讓應(yīng)用能更容易在不同硬件和架構(gòu)上運(yùn) 行。ART 則完全改變了這套做法,在應(yīng)用安裝時就預(yù)編譯字節(jié)碼到機(jī)器語言,這一機(jī)制叫 Ahead-Of-Time (AOT)編譯。在移除解釋代碼這一過程后,應(yīng)用程序執(zhí)行將更有效率,啟動更快。

運(yùn)行時數(shù)據(jù)區(qū)

JVM 運(yùn)行時數(shù)據(jù)結(jié)構(gòu)圖:
runtime-data-access-configuration.png

PC寄存器(PC Register)
也叫程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的信號指示器。
每一條JVM線程都有自己的PC寄存器
在任意時刻,一條 JVM 線程只會執(zhí)行一個方法的代碼。該方法稱為該線程的當(dāng)前方法(Current Method)
如果該方法是 java 方法,那PC寄存器保存 JVM 正在執(zhí)行的字節(jié)碼指令的地址
如果該方法是 native,那 PC 寄存器的值是 undefined。
此內(nèi)存區(qū)域是唯一一個在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。

JVM 棧(Java Virtual Machine Stack)
與 PC 寄存器一樣,java 虛擬機(jī)棧(Java Virtual Machine Stack)也是線程私有的。每一個JVM線程都有自己的java虛擬機(jī)棧,這個棧與線程同時創(chuàng)建,它的生命周期與線程相同,用來保存棧幀。JVM 只會在 JVM 棧上進(jìn)行 push 和 pop 的操作。
JVM stack 可以被實現(xiàn)成固定大小,也可以根據(jù)計算動態(tài)擴(kuò)展。
如果采用固定大小的JVM stack設(shè)計,那么每一條線程的JVM Stack容量應(yīng)該在線程創(chuàng)建時獨立地選定。JVM實現(xiàn)應(yīng)該提供調(diào)節(jié)JVM Stack初始容量的手段。
如果采用動態(tài)擴(kuò)展和收縮的JVM Stack方式,應(yīng)該提供調(diào)節(jié)最大、最小容量的手段。

  • JVM 棧異常情況

  • StackOverflowError:當(dāng)線程請求分配的棧容量超過JVM允許的最大容量時拋出

  • OutOfMemoryError:如果JVM Stack可以動態(tài)擴(kuò)展,但是在嘗試擴(kuò)展時無法申請到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的虛擬機(jī)棧時拋出。

  • 棧幀(stack frame)
    棧幀隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀——無論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異常)都算作方法結(jié)束。棧幀的存儲空間分配在 Java 虛擬機(jī)棧之中,每一個棧幀都有自己的局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)和指向當(dāng)前方法所屬的類的運(yùn)行時常量池的引用。

  • 局部變量數(shù)組(Local variable array)
    每個棧幀內(nèi)部都包含一組稱為局部變量表(Local Variables)的變量列表。棧幀中局部變量表的長度由編譯期決定。
    局部變量使用索引來進(jìn)行定位訪問,第一個局部變量的索引值為零,局部變量的索引值是從零至小于局部變量表最大容量的所有整數(shù)。
    Java 虛擬機(jī)使用局部變量表來完成方法調(diào)用時的參數(shù)傳遞,當(dāng)一個方法被調(diào)用的時候,它的參數(shù)將會傳遞至從 0 開始的連續(xù)的局部變量表位置上。特別地,當(dāng)一個實例方法被調(diào)用的時候,第 0 個局部變量一定是用來存儲被調(diào)用的實例方法所在的對象的引用(即 Java 語言中的“this”關(guān)鍵字)。后續(xù)的其他參數(shù)將會傳遞至從 1 開始的連續(xù)的局部變量表位置上。

  • 操作數(shù)棧(Operand stack)
    每一個棧幀內(nèi)部都包含一個稱為操作數(shù)棧(Operand Stack)的后進(jìn)先出(Last-In-First-Out,LIFO)棧。棧幀中操作數(shù)棧的長度由編譯期決定。
    操作數(shù)棧所屬的棧幀在剛剛被創(chuàng)建的時候,操作數(shù)棧是空的。Java 虛擬機(jī)提供一些字節(jié)碼指令來從局部變量表或者對象實例的字段中復(fù)制常量或變量值到操作數(shù)棧中,也提供了一些指令用于從操作數(shù)棧取走數(shù)據(jù)、操作數(shù)據(jù)和把操作結(jié)果重新入棧。在方法調(diào)用的時候,操作數(shù)棧也用來準(zhǔn)備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。

  • 動態(tài)鏈接(Dynamic Linking)
    每個棧幀都有一個運(yùn)行時常量池的引用。這個引用指向棧幀當(dāng)前運(yùn)行方法所在類的常量池。通過這個引用支持動態(tài)鏈接(dynamic linking)。
    C/C++ 代碼一般被編譯成對象文件,然后多個對象文件被鏈接到一起產(chǎn)生可執(zhí)行文件或者 dll。在鏈接階段,每個對象文件的符號引用被替換成了最終執(zhí)行文件的相對偏移內(nèi)存地址。在 Java中,鏈接階段是運(yùn)行時動態(tài)完成的。
    當(dāng) Java 類文件編譯時,所有變量和方法的引用都被當(dāng)做符號引用存儲在這個類的常量池中。符號引用是一個邏輯引用,實際上并不指向物理內(nèi)存地址。JVM 可以選擇符號引用解析的時機(jī),一種是當(dāng)類文件加載并校驗通過后,這種解析方式被稱為饑餓方式。另外一種是符號引用在第一次使用的時候被解析,這種解析方式稱為惰性方式。無論如何 ,JVM 必須要在第一次使用符號引用時完成解析并拋出可能發(fā)生的解析錯誤。綁定是將對象域、方法、類的符號引用替換為直接引用的過程。綁定只會發(fā)生一次。一旦綁定,符號引用會被完全替換。如果一個類的符號引用還沒有被解析,那么就會載入這個類。每個直接引用都被存儲為相對于存儲結(jié)構(gòu)(與運(yùn)行時變量或方法的位置相關(guān)聯(lián)的)偏移量。

  • 方法正常調(diào)用完成
    在這種場景下,當(dāng)前棧幀承擔(dān)著回復(fù)調(diào)用者狀態(tài)的責(zé)任,其狀態(tài)包括調(diào)用者的局部變量表、操作數(shù)棧和被正確增加過來表示執(zhí)行了該方法調(diào)用指令的程序計數(shù)器等。使得調(diào)用者的代碼能在被調(diào)用的方法返回并且返回值被推入調(diào)用者棧幀的操作數(shù)棧后繼續(xù)正常地執(zhí)行。

  • 方法異常調(diào)用完成
    方法異常調(diào)用完成是指在方法的執(zhí)行過程中,某些指令導(dǎo)致了 Java 虛擬機(jī)拋出異常,并且虛擬機(jī)拋出的異常在該方法中沒有辦法處理,或者在執(zhí)行過程中遇到了 athrow 字節(jié)碼指令顯式地拋出異常,并且在該方法內(nèi)部沒有把異常捕獲住。如果方法異常調(diào)用完成,那一定不會有方法返回值返回給它的調(diào)用者。

本地方法棧(Native method stack)
Java虛擬機(jī)可能會使用到傳統(tǒng)的棧來支持native方法(使用Java語言以外的其它語言編寫的方法)的執(zhí)行,這個棧就是本地方法棧(Native Method Stack)
如果JVM不支持native方法,也不依賴與傳統(tǒng)方法棧的話,可以無需支持本地方法棧。
如果支持本地方法棧,則這個棧一般會在線程創(chuàng)建的時候按線程分配。
異常情況:

  • StackOverflowError:如果線程請求分配的棧容量超過本地方法棧允許的最大容量時拋出
  • OutOfMemoryError:如果本地方法??梢詣討B(tài)擴(kuò)展,并且擴(kuò)展的動作已經(jīng)嘗試過,但是目前無法申請到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的本地方法棧,那Java虛擬機(jī)將會拋出一個OutOfMemoryError異常。

方法區(qū)(Method area)
在Java虛擬機(jī)中,被加載類型的信息都保存在方法區(qū)中。包括類型信息(Type Information)和方法列表(Method Tables)。方法區(qū)是所有線程共享的,所以訪問方法區(qū)信息的方法必須是線程安全的。如果你有兩個線程都去加載一個叫Lava的類,那只能由一個線程被容許去加載這個類,另一個必須等待。
它是在JVM啟動的時候創(chuàng)建的。
存儲了每一個類的結(jié)構(gòu)信息,例如運(yùn)行時常量池(Runtime Constant Pool)、字段和方法數(shù)據(jù)、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容、還包括一些在類、實例、接口初始化時用到的特殊方法。
方法區(qū)的容量可以是固定大小的,也可以隨著程序執(zhí)行的需求動態(tài)擴(kuò)展,并在不需要過多空間時自動收縮。
方法區(qū)在實際內(nèi)存空間中可以是不連續(xù)的。
Java虛擬機(jī)實現(xiàn)應(yīng)當(dāng)提供給程序員或者最終用戶調(diào)節(jié)方法區(qū)初始容量的手段,對于可以動態(tài)擴(kuò)展和收縮方法區(qū)來說,則應(yīng)當(dāng)提供調(diào)節(jié)其最大、最小容量的手段。
是否對方法區(qū)進(jìn)行垃圾回收對JVM的實現(xiàn)是可選的。
Java 方法區(qū)異常:

  • OutOfMemoryError: 如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配請求,那Java虛擬機(jī)將拋出一個OutOfMemoryError異常。

運(yùn)行時常量池(Runtime constant pool)
運(yùn)行時常量池是每一個類或接口的常量池(Constant_Pool)的運(yùn)行時表現(xiàn)形式,它包括了若干種常量:編譯器可知的數(shù)值字面量到必須運(yùn)行期解析后才能獲得的方法或字段的引用。簡而言之,當(dāng)一個方法或者變量被引用時,JVM通過運(yùn)行時常量區(qū)來查找方法或者變量在內(nèi)存里的實際地址。
運(yùn)行時常量池是方法區(qū)的一部分。每一個運(yùn)行時常量池都分配在JVM的方法區(qū)中,在類和接口被加載到JVM后,對應(yīng)的運(yùn)行時常量池就被創(chuàng)建。
在創(chuàng)建類和接口的運(yùn)行時常量池時,可能會遇到的異常:

  • OutOfMemoryError:當(dāng)創(chuàng)建類和接口時,如果構(gòu)造運(yùn)行時常量池所需的內(nèi)存空間超過了方法區(qū)所能提供的最大內(nèi)存空間后就會拋出OutOfMemoryError

堆(Heap)
在 JVM 中,堆(heap)是可供各條線程共享的運(yùn)行時內(nèi)存區(qū)域,也是供所有類實例和數(shù)據(jù)對象分配內(nèi)存的區(qū)域。
Java堆載虛擬機(jī)啟動的時候就被創(chuàng)建,堆中儲存了各種對象,這些對象被自動管理內(nèi)存系統(tǒng)(Automatic Storage Management System,也即是常說的“Garbage Collector(垃圾回收器)”)所管理。這些對象無需、也無法顯示地被銷毀。
Java堆的容量可以是固定大小,也可以隨著需求動態(tài)擴(kuò)展,并在不需要過多空間時自動收縮。
Java堆所使用的內(nèi)存不需要保證是物理連續(xù)的,只要邏輯上是連續(xù)的即可。
JVM實現(xiàn)應(yīng)當(dāng)提供給程序員調(diào)節(jié)Java 堆初始容量的手段,對于可動態(tài)擴(kuò)展和收縮的堆來說,則應(yīng)當(dāng)提供調(diào)節(jié)其最大和最小容量的手段。
Java 堆異常:

  • OutOfMemoryError:如果實際所需的堆超過了自動內(nèi)存管理系統(tǒng)能提供的最大容量時拋出。

堆內(nèi)的內(nèi)存回收—— JVM GC

待續(xù)。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JVM內(nèi)存模型Java虛擬機(jī)(Java Virtual Machine=JVM)的內(nèi)存空間分為五個部分,分別是: ...
    光劍書架上的書閱讀 2,783評論 2 26
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,275評論 0 62
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,822評論 18 399
  • 每個使用Java的開發(fā)者都知道Java字節(jié)碼是在JRE中運(yùn)行(JRE: Java 運(yùn)行時環(huán)境)。JVM則是JRE中...
    燕京博士閱讀 1,664評論 0 6
  • 我回來啦!我的文字天堂,我的精神樂園,答應(yīng)自己一周更一篇文章,有關(guān)醫(yī)學(xué)和人文生活,我要去背書了
    小括號熊閱讀 212評論 0 0

友情鏈接更多精彩內(nèi)容