探索 Java 內(nèi)存管理機(jī)制

首圖.jpg

目錄

  1. 什么是內(nèi)存签财?
  2. 什么是 Java 內(nèi)存模型?
  3. 什么是 JVM淹朋?
  4. JVM 是怎么劃分內(nèi)存的笙各?
  5. 棧幀中的數(shù)據(jù)有什么用?
  6. 什么是可達(dá)性算法础芍?
  7. Java 中有哪幾種引用杈抢?
  8. 什么是垃圾回收器?
  9. 參考文獻(xiàn)

前言

這篇文章是我自己回顧和再學(xué)習(xí) Java 內(nèi)存管理相關(guān)知識(shí)的過程中整理出來的仑性。

整理的目的是讓我自己能對(duì) Java 內(nèi)存管理相關(guān)的知識(shí)的認(rèn)識(shí)更全面一些惶楼,分享的目的是希望大家也能從這些知識(shí)中得到一些啟發(fā)。

1. 什么是內(nèi)存诊杆?

內(nèi)存是計(jì)算機(jī)中重要的部件之一歼捐,是與 CPU 進(jìn)行溝通的橋梁,是 CPU 能直接尋址的存儲(chǔ)空間晨汹,由半導(dǎo)體器件制成豹储。

如果說數(shù)據(jù)是商品,那硬盤就是商店的倉(cāng)庫(kù)淘这,內(nèi)存就是商店的貨架剥扣,倉(cāng)庫(kù)里的商品你是不能直接買的,你只能買貨架上的商品慨灭。

每一個(gè)程序中使用的內(nèi)存區(qū)域相當(dāng)于是不同的貨架朦乏,當(dāng)一個(gè)貨架上需要擺放的商品超過這個(gè)貨架所能容納的最大值,就會(huì)出現(xiàn)放不下的情況氧骤,也就是內(nèi)存溢出呻疹。

2. 什么是 JVM?

JVM(Java 虛擬機(jī))是 Java Virtual Machine 的縮寫筹陵,它是一個(gè)虛構(gòu)出來的計(jì)算機(jī)刽锤,通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來實(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)買貨架上的商品。

3. 什么是 Java 內(nèi)存模型檐什?

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

下面我們就來看下 Java 內(nèi)存模型的具體介紹住册。

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

Java 內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存(Main Memory)中,每條線程有自己的工作內(nèi)存(Working Memory)瓮具,線程的工作內(nèi)存中保存了線程使用到的變量的內(nèi)存副本界弧。

線程對(duì)變量副本的所有操作都必須在工作內(nèi)存中進(jìn)行,不能直接讀寫主內(nèi)存中的變量搭综。

不同線程之間無(wú)法直接訪問其他線程工作內(nèi)存中的變量垢箕,線程間變量值的傳遞都要通過主內(nèi)存來完成。

Java 內(nèi)存模型.png

3.2 執(zhí)行引擎

所謂執(zhí)行引擎兑巾,就是一個(gè)運(yùn)算器条获,能夠識(shí)別輸入的指令,并根據(jù)輸入的指令執(zhí)行一套特定的邏輯蒋歌,最終輸出特定的結(jié)果
執(zhí)行引擎對(duì)于 JVM 的作用就像是 CPU 對(duì)于實(shí)體機(jī)器的作用帅掘,都可以識(shí)別指令,并且根據(jù)指令完成特定的運(yùn)算堂油。

3.3 主內(nèi)存與工作內(nèi)存的交互操作

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

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

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

  1. lock(鎖定)

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

  2. unlock(解鎖)

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

  3. read(讀日占ぁ)
    作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中盹牧,以便 load 時(shí)使用俩垃。

  4. write(寫入)

    作用于主內(nèi)存的變量励幼,它把 store 操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中。

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

  1. load(載入)

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

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

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

  4. store(存儲(chǔ))
    作用于工作內(nèi)存的變量睛榄,它把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中荣茫,以便隨后的 write 操作使用。

4. JVM 是怎么劃分內(nèi)存的场靴?

JVM 在執(zhí)行 Java 程序的過程中會(huì)把它管理的內(nèi)存分為若干個(gè)數(shù)據(jù)區(qū)域啡莉,而這些區(qū)域又可以分為線程私有的數(shù)據(jù)區(qū)域和線程共享的數(shù)據(jù)區(qū)域。

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

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

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

程序計(jì)數(shù)器有下面兩個(gè)特點(diǎn)旨剥。

  • 較小

    程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間咧欣,它可以看作是當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào)指示器。

  • 線程私有

    為了線程切換后能恢復(fù)到正確的執(zhí)行位置轨帜,每條線程都有一個(gè)私有的程序計(jì)數(shù)器魄咕。

  • 無(wú)異常

    程序計(jì)數(shù)器是唯一一個(gè)在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何 OOM 情況的區(qū)域。

4.1.2 虛擬機(jī)棧

虛擬機(jī)棸龈福可以說是 Java 方法棧哮兰,它有下面三個(gè)特點(diǎn)。

  • 描述方法執(zhí)行

    虛擬機(jī)棧描述的是 Java 方法執(zhí)行的內(nèi)存模型苟弛,每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)喝滞,棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧膏秫、動(dòng)態(tài)鏈接右遭、方法出口等信息。

    一個(gè)方法從調(diào)用到執(zhí)行完成的過程缤削,對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程狸演。

    關(guān)于棧幀在第 5 大節(jié)會(huì)有一個(gè)更多的介紹。

  • 線程私有

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

  • 異常

    在 Java 虛擬機(jī)規(guī)范中吨拗,對(duì)虛擬機(jī)棧規(guī)定了下面兩種異常满哪。

    1. StackOverflowError

      當(dāng)執(zhí)行 Java 方法時(shí)會(huì)進(jìn)行壓棧的操作婿斥,在棧中會(huì)保存局部變量、操作數(shù)棧和方法出口等信息哨鸭。

      JVM 規(guī)定了棧的最大深度民宿,如果線程請(qǐng)求執(zhí)行方法時(shí)棧的深度大于規(guī)定的深度,就會(huì)拋出棧溢出異常 StackOverflowError像鸡。

    2. OutOfMemoryError

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

4.1.3 本地方法棧

本地方法棧(Native Method Stack)的作用與虛擬機(jī)棧非常相似只估,它有下面兩個(gè)特點(diǎn)志群。

  • 為 Native 方法服務(wù)

    本地方法棧與虛擬機(jī)棧的區(qū)別是虛擬機(jī)棧為 Java 方法服務(wù),而本地方法棧為 Native 方法服務(wù)蛔钙。

  • 異常

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

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

4.2.1 Java 堆

Java 堆(Java Heap)也就是實(shí)例堆吁脱,它用于存放我們創(chuàng)建的對(duì)象實(shí)例桑涎,它有下面幾個(gè)特點(diǎn)。

  • 最大

    對(duì)于大多數(shù)應(yīng)用來說兼贡,Java 堆是 JVM 管理的內(nèi)存中最大的一塊內(nèi)存區(qū)域攻冷。

  • 線程共享

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

  • 存放實(shí)例

    堆的唯一作用就是存放對(duì)象實(shí)例讲衫,幾乎所有的對(duì)象實(shí)例都是在這里分配內(nèi)存。

  • GC

    堆是垃圾收集器管理的主要區(qū)域孵班,所以有時(shí)也叫 GC 堆涉兽。

4.2.2 方法區(qū)

方法區(qū)(Method Area)存儲(chǔ)的是已被虛擬機(jī)加載的數(shù)據(jù),它有下面幾個(gè)特點(diǎn)篙程。

  • 線程共享

    方法區(qū)和堆一樣枷畏,是所有線程共享的內(nèi)存區(qū)域。

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

    • 類信息
    • 常量
    • 靜態(tài)變量
    • 即時(shí)編譯器編譯后的代碼
  • 異常

    方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類虱饿,如果系統(tǒng)定義了太多的類拥诡,導(dǎo)致方法區(qū)溢出,虛擬機(jī)同樣會(huì)拋出內(nèi)存溢出異常 OutOfMemoryError氮发。

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

  1. 運(yùn)行時(shí)常量池

    運(yùn)行時(shí)常量池(Runtime 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 異常礁苗。

  2. 直接內(nèi)存

    直接內(nèi)存(Direct Memory)有下面幾個(gè)特點(diǎn)爬凑。

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

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

    • 直接分配

      在 JDK 1.4 中新加入了 NIO(New Input/Output)類嘁信,引入了一種基于通道與緩沖區(qū)的 I/O 方式,它可以使用 Native 函數(shù)庫(kù)直接分配堆外內(nèi)存疏叨,然后通過一個(gè)存儲(chǔ)在 Java 堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作潘靖,這樣能避免在 Java 堆和 Native 堆中來回復(fù)制數(shù)據(jù)。

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

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

    • 異常

      直接內(nèi)存的容量默認(rèn)與 Java 堆的最大值一樣携御,如果超額申請(qǐng)內(nèi)存昌粤,也有可能導(dǎo)致 OOM 異常出現(xiàn)。

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

當(dāng) Java 程序出現(xiàn)異常時(shí)涮坐,程序會(huì)打印出對(duì)應(yīng)的異常堆棧,通過這個(gè)堆棧我們可以知道方法的調(diào)用鏈路誓军,而這個(gè)調(diào)用鏈路就是由一個(gè)個(gè) Java 方法棧幀組成的袱讹。

我們來看下棧幀中包含的局部變量表、操作數(shù)棧昵时、動(dòng)態(tài)連接和返回地址分別有著什么作用捷雕。

棧幀.png

5.1 局部變量表

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

局部變量表中存放的編譯期可知的各種數(shù)據(jù)有如下三種句柠。

  1. 基本數(shù)據(jù)類型

    如 boolean浦译、char、int 等

  2. 對(duì)象引用

    reference 類型溯职,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樉眩部赡苁侵赶蛞粋€(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置

  3. returnAddress 類型

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

5.2 操作數(shù)棧

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

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

當(dāng)一個(gè)方法剛開始執(zhí)行時(shí)佳头,操作數(shù)棧是空的,在方法執(zhí)行的過程中晴氨,會(huì)有各種字節(jié)碼執(zhí)行往操作數(shù)棧中寫入和提取內(nèi)容康嘉,也就是出棧/入棧操作。

比如下面的這張圖中籽前,當(dāng)調(diào)用了虛擬機(jī)的 iadd 指令后亭珍,它就會(huì)在操作數(shù)棧中彈出兩個(gè)整數(shù)并進(jìn)行加法計(jì)算,并將計(jì)算結(jié)果入棧枝哄。

操作數(shù)棧.png

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

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

5.4 方法返回地址

當(dāng)一個(gè)方法開始執(zhí)行后挠锥,只有兩種方式可以退出這個(gè)方法众羡,一種是正常完成出口,另一種是異常完成出口蓖租。

  1. 正常完成出口

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

    是否有返回值和返回值的類型將根據(jù)遇到哪種方法返回指令來決定蓖宦,這種退出方法的方式稱為正常完成出口(Normal Method Invocation Completion)齐婴。

  2. 異常完成出口

    在方法執(zhí)行過程中遇到異常,并且這個(gè)異常沒有在方法體內(nèi)得到處理稠茂,就會(huì)導(dǎo)致方法退出柠偶,這種退出方式稱為異常完成出口(Abrupt Method Invocation Completion)。

    一個(gè)方法使用異常完成出口的方式退出睬关,任何值都不會(huì)返回給它的調(diào)用者诱担。

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

6. 什么是可達(dá)性算法藐不?

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

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

當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連時(shí)法梯,則證明此對(duì)象是不可用的。

比如下圖中的 object5、object6立哑、object7夜惭,雖然它們互有關(guān)聯(lián),但是它們到 GC Roots 是不可達(dá)的铛绰,所以它們會(huì)被判定為可回收對(duì)象诈茧。

引用鏈

在 Java 中,不同內(nèi)存區(qū)域中可作為 GC Roots 的對(duì)象包括下面幾種捂掰。

  1. 虛擬機(jī)棧

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

  2. 方法區(qū)

    1. 類靜態(tài)屬性引用的對(duì)象
    2. 常量引用的對(duì)象
  3. 本地方法棧

    本地方法棧中 Native 方法引用的對(duì)象这嚣。

7. Java 中有哪幾種引用鸥昏?

無(wú)論是通過引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量,還是通過可達(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)引用可以直接訪問目標(biāo)對(duì)象涂滴。

  • 不會(huì)回收

    強(qiáng)引用指向的對(duì)象在任何時(shí)候都不會(huì)被系統(tǒng)回收友酱,虛擬機(jī)即使拋出 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 隱患

    如果二次回收后還沒有足夠的內(nèi)存昭伸,就會(huì)拋出內(nèi)存溢出異常梧乘。

  • SoftReference

    在 JDK 1.2 后,Java 提供了 SoftReference 類來實(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 類來實(shí)現(xiàn)弱引用剔桨。

7.4 虛引用

虛引用是最弱的一種引用關(guān)系,它有以下幾個(gè)特點(diǎn)徙融。

  • 無(wú)法獲取

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

  • 收到通知

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

  • PhatomReference

    在 JDK 1.2 后,提供了 PhantomReference 類來實(shí)現(xiàn)虛引用隐轩。

8. 什么是垃圾回收器饺饭?

地上有臟東西是不可避免的,但是天天都要掃地又太麻煩了职车,有沒有什么辦法可以讓我們不用掃地呢瘫俊?

掃地機(jī)器人就可以幫我們做這件事,而垃圾回收器 GC(Garbage Collector)就相當(dāng)于是掃地機(jī)器人悴灵。

我們 Java 開發(fā)者不用像 C++ 開發(fā)者那樣關(guān)心內(nèi)存釋放的問題扛芽,但是我們也不能擋著掃地機(jī)器人的路。

當(dāng)我們操作不當(dāng)導(dǎo)致某塊內(nèi)存泄漏時(shí)积瞒,GC 就不能對(duì)這塊內(nèi)存進(jìn)行回收川尖。

GC 可不是個(gè)好伺候的主,如果你讓“GC 很忙”茫孔,那它就會(huì)讓你“應(yīng)用很卡”叮喳。

拿 Android 來說,進(jìn)行 GC 時(shí)缰贝,所有線程都要暫停馍悟,包括主線程,16ms 是 Android 要求的每幀繪制時(shí)間揩瞪,而當(dāng) GC 的時(shí)間超過 16ms赋朦,就會(huì)造成丟幀的情況,也就是界面卡頓。

垃圾回收器回收資源的方式就是垃圾回收算法宠哄,下面我們來看下四個(gè)主要的垃圾回收算法壹将。

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

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

標(biāo)記-清除算法.png
  • 工作原理

    • 第一步:標(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ù)制算法

為了解決效率問題辛臊,復(fù)制(Copying)收集算法出現(xiàn)了仙粱。

復(fù)制算法.png
  • 工作原理
復(fù)制算法把可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊彻舰。

當(dāng)使用中的這塊內(nèi)存用完了伐割,就把存活的對(duì)象復(fù)制到另一塊內(nèi)存上,然后把已使用的空間一次清理掉刃唤。

這樣每次都是對(duì)半個(gè)內(nèi)存區(qū)域進(jìn)行回收隔心,內(nèi)存分配時(shí)也不用考慮內(nèi)存碎片等復(fù)雜問題。
  • 優(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)算法。

標(biāo)記-整理算法.png
  • 工作原理

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

8.4 分代收集算法

現(xiàn)代商業(yè)虛擬機(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ì)象存活率高、沒有額外空間對(duì)它進(jìn)行擔(dān)保姥宝,就必須使用標(biāo)記-清理或標(biāo)記-整理算法進(jìn)行回收翅萤。

堆內(nèi)存可分為新生區(qū)、養(yǎng)老區(qū)和永久存儲(chǔ)區(qū)三個(gè)區(qū)域腊满。

堆內(nèi)存區(qū)域.png
  1. 新生區(qū)

    新生區(qū)(Young Generation Space)是類的誕生套么、成長(zhǎng)和消亡的區(qū)域。

    新生區(qū)又分為伊甸區(qū)(Eden space)碳蛋、幸存者區(qū)(Survivor space)兩部分胚泌。

    • 伊甸區(qū)

      大多數(shù)情況下,對(duì)象都是在伊甸區(qū)中分配的肃弟,當(dāng)伊甸區(qū)沒有足夠的空間進(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ū)

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

    • 幸存 1 區(qū)

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

  1. 養(yǎng)老區(qū)

    養(yǎng)老區(qū)(Tenure Generation Space)用于保存從新生區(qū)篩選出來的 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ì)象會(huì)直接進(jìn)入養(yǎng)老區(qū),比如很大的數(shù)字和很長(zhǎng)的字符串笔呀。

  2. 永久存儲(chǔ)區(qū)

    永久存儲(chǔ)區(qū)(Permanent Space)是一個(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)存僚匆。

參考文獻(xiàn)

  1. 視頻

    Top團(tuán)隊(duì)大牛帶你玩轉(zhuǎn)Android性能分析與優(yōu)化

  2. 書籍

    《深入理解Java虛擬機(jī)(第2版)》

    《實(shí)戰(zhàn)Java虛擬機(jī)》

    《揭秘 Java 虛擬機(jī)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搭幻,隨后出現(xiàn)的幾起案子白热,更是在濱河造成了極大的恐慌,老刑警劉巖粗卜,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屋确,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡续扔,警方通過查閱死者的電腦和手機(jī)攻臀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門纱昧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刨啸,“玉大人,你說我怎么就攤上這事识脆∩枇” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵灼捂,是天一觀的道長(zhǎng)离例。 經(jīng)常有香客問我,道長(zhǎng)悉稠,這世上最難降的妖魔是什么宫蛆? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮的猛,結(jié)果婚禮上耀盗,老公的妹妹穿的比我還像新娘。我一直安慰自己卦尊,他們只是感情好叛拷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岂却,像睡著了一般忿薇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淌友,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天甲喝,我揣著相機(jī)與錄音跺讯,去河邊找鬼。 笑死寇仓,一個(gè)胖子當(dāng)著我的面吹牛你雌,可吹牛的內(nèi)容都是我干的器联。 我是一名探鬼主播二汛,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拨拓!你這毒婦竟也來了肴颊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渣磷,失蹤者是張志新(化名)和其女友劉穎婿着,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體醋界,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竟宋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了形纺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丘侠。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逐样,靈堂內(nèi)的尸體忽然破棺而出蜗字,到底是詐尸還是另有隱情,我是刑警寧澤脂新,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布挪捕,位于F島的核電站,受9級(jí)特大地震影響争便,放射性物質(zhì)發(fā)生泄漏担神。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一始花、第九天 我趴在偏房一處隱蔽的房頂上張望妄讯。 院中可真熱鬧,春花似錦酷宵、人聲如沸亥贸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)炕置。三九已至,卻和暖如春男韧,著一層夾襖步出監(jiān)牢的瞬間朴摊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工此虑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甚纲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓朦前,卻偏偏與公主長(zhǎng)得像介杆,于是被迫代替她去往敵國(guó)和親鹃操。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355