java的內(nèi)存管理

對(duì)于從事 C/C++ 程序開(kāi)發(fā)的開(kāi)發(fā)人員來(lái)說(shuō)挽荡,在內(nèi)存管理領(lǐng)域,他們既是擁有最高權(quán)力的帝皇,又是從事最基礎(chǔ)工作的勞動(dòng)人民——既擁有每一個(gè)對(duì)象的“所有權(quán)”腋逆,又擔(dān)負(fù)著每一個(gè)對(duì)象生命從開(kāi)始到終結(jié)的維護(hù)責(zé)任旁壮。

對(duì)于 Java 程序員來(lái)說(shuō)监嗜,在虛擬機(jī)的自動(dòng)內(nèi)存管理機(jī)制的幫助下,不在需要為每一個(gè) new 操作去寫配對(duì)的 delete/free 代碼抡谐,而且不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出問(wèn)題裁奇,看起來(lái)由虛擬機(jī)管理內(nèi)存一切都很美好。不過(guò)也正是因?yàn)?Java 程序員把內(nèi)存控制權(quán)交給了 Java 虛擬機(jī)麦撵,一旦出現(xiàn)內(nèi)存泄漏和溢出的問(wèn)題刽肠,如果不了解虛擬機(jī)怎樣使用內(nèi)存的,那排查錯(cuò)誤將會(huì)成為一項(xiàng)異常艱難的工作免胃。

1. 什么是 JVM音五?

JVM(Java 虛擬機(jī))是 Java Virtual Machine 的縮寫,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī)杜秸,通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的放仗。

JVM 有自己的硬件架構(gòu),如處理器撬碟、堆棧诞挨、寄存器等,還有對(duì)應(yīng)分指令系統(tǒng)呢蛤。

假如一個(gè)程序使用的內(nèi)存區(qū)域是一個(gè)貨架惶傻,那 JVM 就相當(dāng)于是一個(gè)淘寶店鋪,它不是真實(shí)存在的貨架其障,但它和真實(shí)貨架一樣可以上架和下架商品银室,而且上架的商品數(shù)量也是有限的。

假如貨架是在深圳励翼,那 JVM 的平臺(tái)無(wú)關(guān)性就相當(dāng)于是客人可以在各個(gè)地方購(gòu)買你在淘寶上發(fā)布的商品蜈敢,不是只有在深圳才能購(gòu)買貨架上的商品。

2. 什么是 Java 內(nèi)存模型汽抚?

Java 內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則抓狭,也就是在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存,以及從內(nèi)存中取出變量這樣的底層細(xì)節(jié)造烁。

下面我們就來(lái)看下 Java 內(nèi)存模型的具體介紹否过。

2.1 主內(nèi)存與工作內(nèi)存

Java 內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)阿紫主內(nèi)存(Main Memory)中午笛,每條線程有自己的工作內(nèi)存(Working Memory),線程的工作內(nèi)存中保存了該線程所使用到的變量的內(nèi)存副本苗桂。

不同線程之間無(wú)法直接訪問(wèn)其他線程工作內(nèi)存中的變量药磺,線程間變量的傳遞都要通過(guò)主內(nèi)存來(lái)完成(如下圖 2-1所有)。

image

圖2-1 Java 內(nèi)存模型

2.2 執(zhí)行引擎

所謂執(zhí)行引擎煤伟,就是一個(gè)運(yùn)算器癌佩,能夠識(shí)別輸入的指令,并根據(jù)輸入的指令執(zhí)行一套特定的邏輯便锨,最終輸入特定的結(jié)果執(zhí)行引擎對(duì)于 JVM 的作用就像是 CPU 對(duì)于實(shí)體機(jī)器的作用驼卖,都可以識(shí)別指令,并且根據(jù)指令完成特定的運(yùn)算鸿秆。

2.3 主內(nèi)存與工作內(nèi)存的交互模型

Java 內(nèi)存模型中定義了 8 種操作完成主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證 每一種操作都是原子級(jí)怎囚、不可再分的卿叽。

這 8 種操作又可分為作用于主內(nèi)存和作用于工作內(nèi)存的操作。

2.3.1 作用于主內(nèi)存的操作

lock(鎖定)

作用于主內(nèi)存的變量恳守,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)考婴。

unlock(解鎖)

作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái)催烘,釋放后的變量才能被其他線程鎖定沥阱。

read(讀取)

作用于主內(nèi)存的變量伊群,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中考杉,以便 laod 時(shí)使用。

write(寫入)

作用于主內(nèi)存的變量舰始,它把 store 操作從工作內(nèi)存中得到的變量值存入到主內(nèi)存變量中崇棠。

2.3.2 作用于工作內(nèi)存的操作

load(載入)

作用于工作內(nèi)存的變量,它把 read 操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中丸卷。

use(使用)

作用于工作內(nèi)存的變量枕稀,它把一個(gè)工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用的變量的值的字節(jié)碼執(zhí)行時(shí)會(huì)執(zhí)行這個(gè)操作谜嫉。

assign(賦值)

作用于工作內(nèi)存的變量萎坷,它把一個(gè)執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)變量賦值的字節(jié)碼執(zhí)行時(shí)執(zhí)行這個(gè)操作沐兰。

store(存儲(chǔ))

作用于工作內(nèi)存的變量哆档,它把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的 write 操作僧鲁。

3. JVM 是怎么劃分內(nèi)存的虐呻?

Java 虛擬機(jī)在執(zhí)行 Java 程序過(guò)程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域象泵。這些區(qū)域都有各自的用途,以及創(chuàng)建和銷毀的時(shí)間斟叼,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在偶惠,有些區(qū)域則是以來(lái)用戶線程的啟動(dòng)和結(jié)束而建立和銷毀。Java 虛擬機(jī)所管理的內(nèi)存將會(huì)包括以下幾個(gè)運(yùn)行區(qū)域朗涩,如圖4-1所示忽孽。

image

圖4-1 Java 虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

3.1 線程私有的數(shù)據(jù)區(qū)

3.1.1 程序計(jì)數(shù)器

程序計(jì)數(shù)器有以下 三個(gè)特點(diǎn):

  • 較小

程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所指向的字節(jié)碼的行號(hào)指示器谢床。在虛擬機(jī)的概念模型里兄一,字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要指向字節(jié)碼指令,分支识腿、循環(huán)出革、跳轉(zhuǎn)、異常處理渡讼、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成骂束。

  • 線程私有

由于 Java 虛擬機(jī)的多線程是通過(guò)線程輪流 切換并分配處理器指向時(shí)間的方式來(lái)實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻成箫,一個(gè)處理器(對(duì)于多核處理器來(lái)說(shuō)是一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程的指令展箱。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置蹬昌,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器混驰,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)皂贩,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存栖榨。

  • 無(wú)異常

如果線程正在執(zhí)行的是一個(gè) Java 的方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址先紫;如果正在執(zhí)行的是 Native 方法治泥,這個(gè)計(jì)數(shù)器值則為空(Undefined)。此內(nèi)存區(qū)域是唯一一個(gè)在 Java 虛擬機(jī)規(guī)范中沒(méi)有任何 OutOfMemoryError 情況的區(qū)域遮精。

3.1.2 虛擬機(jī)棧

  • 線程私有

與程序計(jì)數(shù)器一樣居夹,Java 虛擬機(jī)棧也是線程私有的,它的生命周期與線程相同本冲。

  • 描述 Java 方法執(zhí)行的內(nèi)存模型

虛擬機(jī)棧描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame准脂,棧幀是方法運(yùn)行期的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu))用于存儲(chǔ)局部變量表、操作棧檬洞、動(dòng)態(tài)鏈接狸膏、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程添怔,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧棧從入棧到出棧的過(guò)程湾戳。

  • 異常

在 Java 虛擬中贤旷,對(duì)虛擬機(jī)規(guī)定了下面兩種異常:

StockOverflowError

當(dāng)執(zhí)行 Java 方法是會(huì)進(jìn)行壓棧的操作,在棧棧會(huì)保存局部變量砾脑、操作棧和方法出口等信息幼驶。

JVM 規(guī)定了棧的最大勝讀,如果線程請(qǐng)求執(zhí)行方法時(shí)棧的深度大于規(guī)定的深度韧衣,就會(huì)拋出棧溢出異常 StockOverflowError盅藻。

OutOfMemoryError

如果虛擬機(jī)在擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出內(nèi)存溢出異常 OutOfMemoryError畅铭。

3.1.3 本地方法棧

本地方法棧的作用于虛擬機(jī)非常相似氏淑,它有下面兩個(gè)特點(diǎn)。

① 為 native 服務(wù)

本地方法棧與虛擬機(jī)棧的區(qū)別是虛擬機(jī)棧為 Java 服務(wù)硕噩,而本地方法棧為 native 方法服務(wù)假残。

② 異常

與虛擬機(jī)棧一樣,本地方法棧也會(huì)拋出 StackOverflowError 和 OutOfMemoryError 異常炉擅。

3.2 所有線程共享的數(shù)據(jù)區(qū)域

3.2.1 Java 堆

Java 堆(Java Heap)也就是實(shí)例堆守问,它有以下四個(gè)特點(diǎn):

① 最大

對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō),Java 堆(Java Heap)是 Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊坑资。

② 線程共享

Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建穆端。

③ 存放實(shí)例

此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象的實(shí)例袱贮,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。這一點(diǎn)在 Java 虛擬機(jī)規(guī)范中的描述是:所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配体啰,但是隨著 JIT 編譯器的發(fā)展與逃逸分析技術(shù)的逐漸成熟攒巍,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)送荒勇,所有的對(duì)象都分配在堆上也漸漸變得不那么“絕對(duì)”了 柒莉。

④ GC

Java 堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱為“GC堆”(Carbage Collected Heap)沽翔。如果從內(nèi)存回收的角度看兢孝,由于現(xiàn)在收集器基本都是采用的分代收集算法(詳見(jiàn)XXX),所以 Java 堆中還可以細(xì)分為:新生代和老年代仅偎。如果從內(nèi)存分配的角度看跨蟹,線程共享的 Java 堆中可能劃分多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)橘沥。不過(guò)窗轩,無(wú)論如何劃分,都與存放內(nèi)容無(wú)關(guān)座咆,無(wú)論哪個(gè)區(qū)域痢艺,存儲(chǔ)的都依然是對(duì)象實(shí)例仓洼,進(jìn)一步劃分的目的是為了更好地回收內(nèi)存,或者更快地分配內(nèi)存堤舒。

3.2.2 方法區(qū)

方法區(qū)存儲(chǔ)的是已經(jīng)被虛擬機(jī)加載的數(shù)據(jù)色建,它有以下三個(gè)特點(diǎn):

① 線程共享

方法區(qū)域 Java 堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域植酥,它用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的等數(shù)據(jù)镀岛。

② 存儲(chǔ)的數(shù)據(jù)類型

類的信息;

常量友驮;

**◇ **靜態(tài)變量漂羊;

即時(shí)編譯器編譯后的代碼,等卸留。

③ 異常

方法區(qū)的大小決定履歷系統(tǒng)可以保存多少個(gè)類走越,如果系統(tǒng)定義了太多的類,導(dǎo)致方法區(qū)溢出耻瑟,虛擬機(jī)同樣拋出內(nèi)存溢出異常 OutOfMemoryError旨指。

方法區(qū)又可以分為運(yùn)行時(shí)常量池和直接內(nèi)存兩部分。

① 運(yùn)行常量池

運(yùn)行時(shí)常量池(Running Constant Pool)是方法區(qū)的一部分喳整。

Class 文件中處了有類的 版本谆构、字段、方法和接口等描述信息框都,還有一項(xiàng)信息就是常量池(Constant Pool Table)搬素。

常量池用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。

運(yùn)行時(shí)常量池受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)就會(huì)拋出 OutOfMemoryError 異常夺欲。

② 直接內(nèi)存

直接內(nèi)存(Direct Memory)有以下四個(gè)特點(diǎn):

a)在虛擬機(jī)數(shù)據(jù)區(qū)外

直接內(nèi)存不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是 Java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域粱哼。

b)直接分配

在 JDL1.4 中新加入的 NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的 I/O 方式檩咱,它可以使用 native 函數(shù)庫(kù)直接分配堆外內(nèi)存揭措,然后通過(guò)一個(gè)存儲(chǔ)在 Java 堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用操作,這樣能避免在 Java 堆和 native 堆中來(lái)回復(fù)制數(shù)據(jù)刻蚯。

c)受設(shè)備內(nèi)存大小限制

直接內(nèi)存的分配不會(huì)受到 Java 堆大小的限制蜂筹,但是會(huì)受到設(shè)備總內(nèi)存(RAM 以及 SWAP 區(qū))大小以及處理器尋址空間的限制。

d)異常

直接內(nèi)存的容量默認(rèn)與 Java 對(duì)的最大值一樣芦倒,如果超額申請(qǐng)內(nèi)存艺挪,也可能導(dǎo)致 OOM 異常出現(xiàn)。

4. 對(duì)象訪問(wèn)

介紹完 Java 虛擬機(jī)的運(yùn)行時(shí)數(shù)據(jù)區(qū)之后,我們可以探討一個(gè)問(wèn)題:在 Java 語(yǔ)言中麻裳,對(duì)象訪問(wèn)是如何進(jìn)行的口蝠?對(duì)象訪問(wèn)在 Java 中中悟出不在,是最普通的程序行為津坑,但即使是最簡(jiǎn)單的訪問(wèn)妙蔗,也會(huì)涉及 Java 棧、Java 堆疆瑰、方法區(qū)這三個(gè)最重要內(nèi)存區(qū)域之間的關(guān)聯(lián)關(guān)系眉反,如下面這段代碼:

<pre style="margin: 0px; padding: 0px; overflow: auto; font-family: "Courier New"; font-size: 12px; overflow-wrap: break-word; white-space: pre-wrap;">Object obj = new Object();</pre>

假如這句代碼出現(xiàn)在方法體中,那“Object obj”這部分的寓意兼顧會(huì)反映到 Java 棧的本地變量表中穆役,作為一個(gè) reference 類型數(shù)據(jù)出現(xiàn)寸五。而“new Object()”這部分的語(yǔ)義將會(huì)反映 到 Java 堆中,形成一塊存儲(chǔ)了 Object 類型所有實(shí)例數(shù)據(jù)值(Instance Data耿币,對(duì)象中各個(gè)實(shí)例字段的數(shù)據(jù))的結(jié)構(gòu)化內(nèi)存梳杏,根據(jù)具體類型以及虛擬機(jī)實(shí)現(xiàn)的對(duì)象內(nèi)存布局(Object Memory Layout)的不同,這塊內(nèi)存的長(zhǎng)度是不固定的淹接。另外十性,在 Java 堆中還必須包含能查找到此對(duì)象類型數(shù)據(jù)(如對(duì)象類型、父類塑悼、實(shí)現(xiàn)的接口劲适、方法等)的地址信息,這些類型數(shù)據(jù)則存儲(chǔ)在方法區(qū)中厢蒜。

由于 reference 類型在 Java 虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊眉跸欤](méi)有定義這個(gè)引用應(yīng)該通過(guò)哪些方法區(qū)定位,以及訪問(wèn)到 Java 堆中的對(duì)象的具體位置郭怪,因此不同虛擬機(jī)的對(duì)訪問(wèn)方式會(huì)有所不同,主流的訪問(wèn)方法有兩種:使用句柄和直接指針刊橘。

  • 如果使用句柄訪問(wèn)方式鄙才,Java 堆中將會(huì)劃分出一塊內(nèi)存作為句柄池,reference 中存儲(chǔ)的就是對(duì)象的句柄地址促绵,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息攒庵,如圖4-1所示。
image

圖4-1 通過(guò)句柄訪問(wèn)對(duì)象

  • 如果使用直接指針訪問(wèn)方式败晴,Java 堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息浓冒,reference 中直接存儲(chǔ)的就是對(duì)象地址,如圖4-2所示尖坤。
image

圖4-2 通過(guò)直接指針獲取對(duì)象

兩種對(duì)象的訪問(wèn)方式各有優(yōu)勢(shì)稳懒,使用句柄訪問(wèn)方式的最大好處就是 reference 中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針慢味,而 reference 本身不需要被修改场梆。

使用直接指針訪問(wèn)方式的最大好處就是速度更快墅冷,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷,由于對(duì)象的訪問(wèn)在 Java 中非常頻繁或油,因此這類開(kāi)銷積少成多也是一項(xiàng)非衬蓿可觀的執(zhí)行成本。

5. 棧幀中的數(shù)據(jù)有什么用顶岸?

當(dāng) Java 程序出現(xiàn)異常時(shí)腔彰,程序會(huì)打印出對(duì)于的異常堆棧,通過(guò)這個(gè)堆棧我們可以知道方法的調(diào)用鏈路辖佣,而這個(gè)調(diào)用鏈路就是有一個(gè)個(gè) Java 方法棧幀組成霹抛。

下面來(lái)看下棧幀包含的局部變量表、操作數(shù)棧凌简、動(dòng)態(tài)連接和返回地址分別有著什么作用:

image

圖5-1 棧幀

5.1 局部變量表

局部變量表(Local Variable Table)中的變量只在當(dāng)前函數(shù)調(diào)用中有效上炎,當(dāng)函數(shù)調(diào)用結(jié)束后,隨著函數(shù)棧幀的銷毀雏搂,局部變量表也會(huì)隨之銷毀藕施。

局部變量表中存放著編譯期可知的各種數(shù)據(jù),如以下三種:

① 基本數(shù)據(jù)類型

如 boolean凸郑、char裳食、int等。

② 對(duì)象引用

reference 類型芙沥,可能是一個(gè)執(zhí)行對(duì)象起始地址的引用指針诲祸,也可能是指向一個(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置。

③ returnAddress 類型

指向了一條字節(jié)碼指令的地址而昨。

5.2 操作數(shù)棧

操作數(shù)棧(Operand Stack)也叫操作棧救氯,它主要用于保存計(jì)算過(guò)程的中間結(jié)果,同時(shí)作為計(jì)算過(guò)程中臨時(shí)變量的存儲(chǔ)空間歌憨。

操作數(shù)棧也是一個(gè)先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu)着憨,只支持入棧和出棧兩種操作。

當(dāng)一個(gè)方法剛開(kāi)執(zhí)行時(shí)务嫡,操作數(shù)是空的甲抖,在方法執(zhí)行的過(guò)程中,會(huì)有各種字節(jié)碼執(zhí)行往操作數(shù)棧中寫入和提取內(nèi)容心铃,也就是出棧/入棧操作准谚。

如下面的這張圖(如圖5-2所示)中,當(dāng)調(diào)用了虛擬機(jī)額 iadd 指令后去扣,它就會(huì)在操作數(shù)棧彈出兩個(gè)整數(shù)并進(jìn)行加法計(jì)算柱衔,并將計(jì)算結(jié)果入棧。

image

圖5-2 iadd 指令與操作數(shù)棧的變化

5.3 動(dòng)態(tài)連接

每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬非法的引用,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接(Dynamic Linking)秀存。

5.4 方法返回地址

當(dāng)一個(gè)方法開(kāi)始執(zhí)行后捶码,只有兩種方式可以退出這個(gè)方法,一種是正常調(diào)用完成或链,另一種是異常調(diào)用完成惫恼。

① 正常調(diào)用完成

執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令,這時(shí)候可能會(huì)有返回值傳遞給上層方法調(diào)用者澳盐。

是否有返回值和返回值的類型將根據(jù)遇到哪種方法返回指令來(lái)決定祈纯,這種退出方法的方式成為正常調(diào)用完成(Normal Method Invocation Completion)。

**② 異常調(diào)用完成 **

在方法執(zhí)行過(guò)程中遇到異常叼耙,并且這個(gè)異常沒(méi)有在方法體內(nèi)得到處理腕窥,就會(huì)導(dǎo)致方法退出,這種退出方式成為異常調(diào)用完成(Abrupt Method Invocation Completion)

一個(gè)方法使用異常調(diào)用完成 的方法退出筛婉,任何值都不會(huì)返回給它的調(diào)用者簇爆。

無(wú)論采用哪種退出方式,在方法退出后爽撒,都需要返回到方法被調(diào)用的位置入蛆,程序才能繼續(xù)執(zhí)行。

6. 什么是可達(dá)性算法硕勿?

在主流的商用程序語(yǔ)言(Java哨毁、C#等)的主流實(shí)現(xiàn)中,都是通過(guò)可達(dá)性分析(Reachability Analysis)判定對(duì)象是否存活源武。

這個(gè)算法的基本思路就是通過(guò)一系列“GC Roots”對(duì)象作為起始點(diǎn)扼褪,從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索走過(guò)的路徑就叫引用鏈粱栖。

當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有與任何引用鏈相連時(shí)话浇,則證明此對(duì)象是無(wú)用的。

比如闹究,下圖(圖6-1所示)中的 object6幔崖, object7, object8跋核, 雖然它們相互關(guān)聯(lián),但是衙門到 GC Roots 是不可達(dá)的所以它們會(huì)被判定為可回收對(duì)象叛买。

image

圖6-1 可達(dá)性算法判斷對(duì)象是否可回收

在 Java 中砂代,不同內(nèi)存區(qū)域可作為 GC Roots 的對(duì)象的包括以下三種:

① 虛擬機(jī)棧

虛擬機(jī)棧的棧幀中的局部變量表中引用的對(duì)象,比如某個(gè)方法正在使用的類字段率挣。

② 方法區(qū)

◆ 類靜態(tài)屬性引用的對(duì)象刻伊;

◆ 常量引用的對(duì)象。

③ 本地方法棧

本地方法棧棧 native 方法引用的對(duì)象。

7. Java 中有哪幾種引用捶箱?

無(wú)論是通過(guò)引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量智什,還是通過(guò)可達(dá)性分析算法判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá),判斷對(duì)象是否存活都與引用有關(guān)丁屎。

在 JDK 1.2 之后荠锭,Java 對(duì)引用的改良進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用晨川、軟引用证九、弱引用、虛引用四種共虑,這四種引用強(qiáng)度依次減弱愧怜。

7.1 強(qiáng)引用

強(qiáng)引用有以下四個(gè)特點(diǎn):

① 普遍存在

強(qiáng)引用是指代碼中普遍存在的,比如“Object obj = new Object()”這類引用妈拌。

② 直接引用

強(qiáng)引用可以直接訪問(wèn)目標(biāo)對(duì)象拥坛。

③ 不會(huì)回收

強(qiáng)引用指向的對(duì)象在任何時(shí)候都不會(huì)被系統(tǒng)回收,虛擬機(jī)及時(shí)拋出 OOM 異常尘分,也不會(huì)回收強(qiáng)引用指向的對(duì)象猜惋。

使用 obj=null 不會(huì)觸發(fā) GC,但是在下次GC的時(shí)候音诫,這個(gè)強(qiáng)引用對(duì)象就可以被回收了惨奕。

④ OOM 隱患

強(qiáng)引用可能導(dǎo)致內(nèi)存泄漏。

7.2 軟引用

軟引用有以下四個(gè)特點(diǎn):

① 有用但非必需

軟引用用于描述一些還有用但非必需的對(duì)象竭钝。

② 二次回收

對(duì)于軟引用關(guān)聯(lián)的對(duì)象梨撞,在系統(tǒng)即將發(fā)生內(nèi)存溢出前,會(huì)把這些對(duì)象列入回收范圍中進(jìn)行二次回收香罐。

③ OOM 隱患

如果二次回收后還沒(méi)有足夠的內(nèi)存卧波,就會(huì)拋出內(nèi)存溢出異常。

④ SoftReference

在 JDK 1.2 后庇茫,Java 提供了 SoftReference 類來(lái)實(shí)現(xiàn)軟引用港粱。

7.3 弱引用

軟引用有以下四個(gè)特點(diǎn)

① 比軟引用更弱

弱引用的強(qiáng)度比軟引用一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次 GC 前旦签。

② 發(fā)現(xiàn)即回收

在 GC 時(shí)查坪,只要發(fā)現(xiàn)弱引用,不管系統(tǒng)堆空間使用情況如何宁炫,都會(huì)將對(duì)象進(jìn)行回收偿曙。

③ 可有可無(wú)

軟引用、弱引用適合保存可有可無(wú)的緩存數(shù)據(jù)羔巢。

④ WeakReference

JDK 1.2 后望忆,提供了 WeakReference 類來(lái)實(shí)現(xiàn)弱引用。

7.4 虛引用

虛引用是最弱的一種引用關(guān)系启摄,它有以下三個(gè)特點(diǎn):

① 無(wú)法獲取

一個(gè)對(duì)象無(wú)論是否有虛引用的存在,都不會(huì)對(duì)其生成時(shí)間構(gòu)成影響歉备,也無(wú)法通過(guò)虛引用取得一個(gè)對(duì)象實(shí)例。

② 收到通知

為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被垃圾回收器回收時(shí)收到一個(gè)系統(tǒng)通知落午。

③ PhantomReference

在 JDK 1.2 后,提供了 PhantomReference 類來(lái)實(shí)現(xiàn)虛引用溃斋。

8. 什么是垃圾回收器?

如果說(shuō)收集算法是內(nèi)存回收的方法論梗劫,那么垃圾回收器就是內(nèi)存回收的具體實(shí)現(xiàn)。我們 Java 開(kāi)發(fā)者不用像 C++ 開(kāi)發(fā)者那樣關(guān)心內(nèi)存釋放的問(wèn)題截碴,但我們也不能胡亂操作梳侨。當(dāng)我們操作不當(dāng)導(dǎo)致某塊內(nèi)存泄漏時(shí),GC 就不能對(duì)這塊內(nèi)存進(jìn)行回收日丹。

GC 可不是好伺候的主走哺,如果你讓“GC 很忙”,那它就會(huì)讓你“應(yīng)用很卡”哲虾。

拿 Android 來(lái)說(shuō)丙躏,進(jìn)行 GC 時(shí),所有線程都要暫停束凑,包括主線程晒旅,16ms 是Android 要求的每幀繪制時(shí)間,而當(dāng) GC 的時(shí)間超過(guò) 16ms汪诉,就會(huì)造成丟幀的情況废恋,也就是界面卡頓。

垃圾回收器回收資源的方法就是垃圾回收算法扒寄,下面我們來(lái)看下四個(gè)主要的垃圾回收算法鱼鼓。

8.1 標(biāo)記-清除算法

標(biāo)記-清除算法(Mark-Sweep)相當(dāng)于是先把貨架上有人買的、沒(méi)人買的该编、空著的商品和位置都記錄下來(lái)迄本,然后再把沒(méi)人買的商品統(tǒng)一進(jìn)行下架。

image

圖8-1 標(biāo)記-清除算法

  • 工作原理

第一步:標(biāo)記所有需要回收的對(duì)象上渴;

第二步:標(biāo)記完成后岸梨,統(tǒng)一回收所有被標(biāo)記的對(duì)象。

  • 缺點(diǎn)

效率低

標(biāo)記和清除的效率都不高稠氮。

內(nèi)存碎片

標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片曹阔,內(nèi)存碎片大多會(huì)導(dǎo)致當(dāng)程序需要分配較大的對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā) GC隔披。

8.2 復(fù)制算法

為了解決效率問(wèn)題赃份,復(fù)制(Copying)收集算法出現(xiàn)了抓韩。

image

圖8-2 復(fù)制算法

  • 工作原理

復(fù)制算法把可用內(nèi)存按容量劃分為大小相同的兩塊谒拴,每次只使用其中的一塊英上。

當(dāng)使用中的這塊內(nèi)存用完了啤覆,就把存活的對(duì)象復(fù)制到另一塊內(nèi)存上窗声,然后把已使用的控件一次清理掉。

這樣每次都是對(duì)半個(gè)內(nèi)存區(qū)域進(jìn)行回收拦耐,內(nèi)存分配時(shí)也不用考慮內(nèi)存碎片等復(fù)雜問(wèn)題揩魂。

  • 優(yōu)點(diǎn)

復(fù)制算法的優(yōu)點(diǎn)是每次只對(duì)半個(gè)內(nèi)存區(qū)域進(jìn)行內(nèi)存回收炮温,分配內(nèi)存時(shí)也不用考慮內(nèi)存碎片等復(fù)雜情況柒啤,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可方援。

  • 缺點(diǎn)

浪費(fèi)空間

把內(nèi)存縮小一半用太浪費(fèi)空間犯戏。

有時(shí)效率低

在對(duì)象存活率高時(shí),要進(jìn)行較多的復(fù)制操作先匪,這時(shí)效率就低了呀非。

8.3 標(biāo)記-整理算法

在復(fù)制算法中,如果不想浪費(fèi) 50% 的空間猖败,就需要有額外的空間進(jìn)行分配擔(dān)保恩闻,以應(yīng)對(duì)被使用內(nèi)存中所有對(duì)象都存活的低端情況所以養(yǎng)老區(qū)不能用這種算法剧董。

根據(jù)養(yǎng)老區(qū)的特點(diǎn),有人提出了一種標(biāo)記-整理(Mark-Compact)算法侠草。

image

圖8-3 標(biāo)記-整理算法

  • 工作原理

標(biāo)記-整理算法的標(biāo)記過(guò)程與標(biāo)記-清除算法一樣边涕,但后續(xù)步驟是讓所有存活的對(duì)象向一端移動(dòng)功蜓,然后直接清除掉邊界外的內(nèi)存宠蚂。

8.4 分代收集算法

現(xiàn)代商業(yè)虛擬機(jī)的垃圾回收機(jī)制都是采用分代收集算法(Generational Collection)算法求厕,這種算法會(huì)根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊,這樣就可以根據(jù)各個(gè)區(qū)域的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>

在新生區(qū)美浦,每次垃圾收集都有大批對(duì)象死去项栏,只有少了存活沼沈,所以可以用復(fù)制算法币厕。

養(yǎng)老區(qū)中因?yàn)閷?duì)象存活率高劈榨、沒(méi)有額外空間對(duì)它進(jìn)行擔(dān)保,就必須使用標(biāo)記-清除或標(biāo)記-整理算法進(jìn)行回收拷姿。

對(duì)內(nèi)存可分為新生區(qū)、養(yǎng)老區(qū)永久存儲(chǔ)區(qū)三個(gè)區(qū)域描滔。

image

圖8-4 堆存儲(chǔ)的三個(gè)區(qū)域

8.4.1 新生區(qū)(Young Generation Space)

新生區(qū)是類的誕生含长、成長(zhǎng)拘泞、消亡的區(qū)域枕扫。

新生區(qū)有分為伊甸區(qū)、幸存者區(qū)兩部分诗鸭。

  • 伊甸區(qū)(Eden Space)

大多數(shù)情況下强岸,對(duì)象都是在伊甸區(qū)中分配的砾赔,當(dāng)伊甸區(qū)沒(méi)有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次 Minor GC十绑。

Minor GC 是指發(fā)生在新生區(qū)的垃圾回收動(dòng)作本橙,Minor GC 非常繁忙脆诉,回收速度也比較快贷币。

當(dāng)伊甸區(qū)的空間用完時(shí)役纹,GC 會(huì)對(duì)伊甸區(qū)進(jìn)行垃圾回收促脉,然后把伊甸區(qū)剩下的對(duì)象移動(dòng)到幸存0區(qū)策州。

  • 幸存0區(qū)(Survivor 0 Space)

如果幸存0區(qū)滿了,GC 會(huì)對(duì)該區(qū)域進(jìn)行垃圾回收旁仿,然后把該 區(qū)域剩下的對(duì)象移動(dòng)到幸存1區(qū)枯冈。

  • 幸存1區(qū)(Survivor 1 Space)

如果幸存1區(qū)滿了办悟,GC 會(huì)對(duì)該區(qū)域進(jìn)行垃圾回收,然后把幸存1區(qū)中的對(duì)象移動(dòng)到養(yǎng)老區(qū)罪既。

8.4.2 養(yǎng)老區(qū)(Tenure Generation Space)

養(yǎng)老區(qū)用于保存從新生區(qū)篩選出來(lái)的 Java 對(duì)象琢感。

當(dāng)幸存1區(qū)嘗試移動(dòng)對(duì)象到養(yǎng)老區(qū)探熔,但是發(fā)現(xiàn)空間不足時(shí)诀艰,虛擬機(jī)會(huì)發(fā)起一次 Major GC。

Major GC 的速度一般比 Minor GC 慢 10 倍以上苛蒲。

大對(duì)象直接進(jìn)入養(yǎng)老區(qū)臂外,比如很大的數(shù)字和很長(zhǎng)的字符串。

8.4.3 永久存儲(chǔ)區(qū)(Permanent Space)

永久存儲(chǔ)區(qū)是一個(gè)常駐內(nèi)存區(qū)域嚎货,用于存放 JDK 自身攜帶的 Class Interface 元數(shù)據(jù)蔫浆。

永久存儲(chǔ)區(qū)存儲(chǔ)的是運(yùn)行環(huán)境必需的類信息瓦盛,被裝載進(jìn)該區(qū)域的數(shù)據(jù)是不會(huì)被垃圾回收器回收掉的,只有 JVM 關(guān)閉時(shí)才會(huì)釋放此區(qū)域的內(nèi)存挠唆。

--轉(zhuǎn)自:https://www.cnblogs.com/steffen/p/11368018.html扮念,感謝原作者寫出這么優(yōu)秀的技術(shù)文章柜与,甚是喜愛(ài)弄匕,所以轉(zhuǎn)載沽瞭!如有侵權(quán)驹溃,給我留言,馬上刪除亡哄!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚊惯,一起剝皮案震驚了整個(gè)濱河市截型,隨后出現(xiàn)的幾起案子儒溉,更是在濱河造成了極大的恐慌,老刑警劉巖笼平,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寓调,死亡現(xiàn)場(chǎng)離奇詭異锄码,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)痛悯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門载萌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扭仁,“玉大人厅翔,你說(shuō)我怎么就攤上這事⌒鼙茫” “怎么了顽分?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵怯邪,是天一觀的道長(zhǎng)花墩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冰蘑,這世上最難降的妖魔是什么祠肥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮东羹,結(jié)果婚禮上属提,老公的妹妹穿的比我還像新娘美尸。我一直安慰自己,他們只是感情好恕酸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布蕊温。 她就那樣靜靜地躺著义矛,像睡著了一般症革。 火紅的嫁衣襯著肌膚如雪鸯旁。 梳的紋絲不亂的頭發(fā)上铺罢,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天韭赘,我揣著相機(jī)與錄音势就,去河邊找鬼。 笑死袖牙,一個(gè)胖子當(dāng)著我的面吹牛鞭达,可吹牛的內(nèi)容都是我干的畴蹭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼繁扎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锻离!你這毒婦竟也來(lái)了汽纠?” 一聲冷哼從身側(cè)響起傀履,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钓账,失蹤者是張志新(化名)和其女友劉穎梆暮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體偿荷,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跳纳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年寺庄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了力崇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡馍盟,死狀恐怖朽合,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宪彩,我是刑警寧澤讲婚,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布筹麸,位于F島的核電站,受9級(jí)特大地震影響白指,放射性物質(zhì)發(fā)生泄漏告嘲。R本人自食惡果不足惜奖地,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一参歹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧僧界,春花似錦捎泻、人聲如沸埋哟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吹截,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晨逝,已是汗流浹背懦铺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工冬念, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留急前,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓统求,卻偏偏與公主長(zhǎng)得像码邻,于是被迫代替她去往敵國(guó)和親另假。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容