溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

java內(nèi)存管理機(jī)制剖析(一)

發(fā)布時(shí)間:2020-08-04 14:59:16 來(lái)源:ITPUB博客 閱讀:182 作者:支付寶技術(shù)團(tuán)隊(duì) 欄目:編程語(yǔ)言

最近利用工作之余學(xué)習(xí)研究了一下java的內(nèi)存管理機(jī)制,在這里記錄總結(jié)一下。

1.1 java內(nèi)存區(qū)域

當(dāng)java程序運(yùn)行時(shí),java虛擬機(jī)會(huì)將內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域,這些內(nèi)存區(qū)域創(chuàng)建和銷(xiāo)毀的時(shí)間各不相同,所承擔(dān)的功能也不相同,他們各司其職,各盡所責(zé)。這些區(qū)域的劃分如下圖
java內(nèi)存管理機(jī)制剖析(一)

運(yùn)行時(shí)數(shù)據(jù)區(qū)主要有五個(gè)區(qū),分別是 堆 ,方法區(qū),虛擬機(jī)棧,本地方法棧,程序計(jì)數(shù)器 ,下面我來(lái)一一詳細(xì)講解這五個(gè)數(shù)據(jù)區(qū)

java堆是java虛擬機(jī)管理內(nèi)存中最大的一塊,它是被所有線(xiàn)程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建, 此內(nèi)存的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)組都在堆分配內(nèi)存 。

java虛擬機(jī)規(guī)定,java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)即可。在實(shí)現(xiàn)時(shí),既可以實(shí)現(xiàn)固定大小的,也可以是擴(kuò)展的,可以 通過(guò)配置-Xmx和-Xms來(lái)擴(kuò)展大小 。如果堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出 OutOfMemoryError

方法區(qū)

方法區(qū)也是被所有線(xiàn)程共享的一塊內(nèi)存區(qū)域,在Java虛擬機(jī)規(guī)范中,方法區(qū)是堆的邏輯組成部分,但他又被與堆區(qū)分開(kāi)來(lái),別名稱(chēng)為Non-Heap,它主要的存儲(chǔ)內(nèi)容有下面幾點(diǎn)

  • 類(lèi)型的完整有效名
  • 類(lèi)型直接父類(lèi)的完整有效名
  • 類(lèi)型的修飾符(public,abstract,final的某個(gè)子集)
  • 類(lèi)型的常量池
  • 域(Field)信息
  • 方法(Method)信息
  • 除了常量外的所有靜態(tài)(static)變量

總結(jié)起來(lái)就是主要用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息,常量,靜態(tài)變量,編譯器編譯后的代碼等數(shù)據(jù)

這里我在介紹一下常量池,域信息和方法信息

—-常量池

常量池也稱(chēng)為運(yùn)行時(shí)常量池(Runtime Constant Pool),用于存放編譯期生成的各種字面量和符號(hào)引用,它是這個(gè)類(lèi)型用到的常量的一個(gè)有序集合,包括 實(shí)際的常量(String, Integer, 和Floating point常量)和類(lèi)型,域和方法的符號(hào)引用 。
池中的數(shù)據(jù)項(xiàng)像數(shù)組項(xiàng)一樣,是通過(guò)索引訪(fǎng)問(wèn)的。 因?yàn)槌A砍卮鎯?chǔ)了一個(gè)類(lèi)類(lèi)型所使用到的所有類(lèi)型,域和方法的符號(hào)引用,所以它在java程序的動(dòng)態(tài)鏈接中起了核心的作用

—-域(Field)信息

域的相關(guān)信息包括: 域名; 域類(lèi)型; 域修飾符(public, private, protected,static,final volatile,transient的某個(gè)子集)

—-方法(Method)信息

方法的相關(guān)信息包括: 方法名, 方法的返回類(lèi)型(或 void), 方法參數(shù)的數(shù)量和類(lèi)型(有序的),方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個(gè)子集) ,除了abstract和native方法外,其他方法還有保存方法的 字節(jié)碼(bytecodes)操作數(shù)棧和方法棧幀的局部變量區(qū)的大小 。

java虛擬機(jī)規(guī)范對(duì)方法區(qū)的限制比較寬松,除了和java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不是實(shí)現(xiàn)垃圾收集。垃圾收集行為在方法區(qū)也比較少出現(xiàn),當(dāng)方法區(qū)無(wú)法滿(mǎn)足內(nèi)存分配時(shí),會(huì)拋出 OutOfMemoryError

虛擬機(jī)棧

虛擬機(jī)棧是線(xiàn)程私有的,它的生命周期與線(xiàn)程相同,當(dāng)我們start一個(gè)線(xiàn)程時(shí),jvm會(huì)為當(dāng)前線(xiàn)程開(kāi)辟一塊虛擬機(jī)棧,當(dāng)當(dāng)前線(xiàn)程死亡時(shí),線(xiàn)程的虛擬機(jī)棧也會(huì)銷(xiāo)毀。

代碼中每個(gè)方法在執(zhí)行的同時(shí),都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息。每一個(gè)方法調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。

局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類(lèi)型(boolean,byte,char,short,int,float,long,double),對(duì)象引用和returnAddreass類(lèi)型

JVM對(duì)這個(gè)區(qū)域規(guī)定了兩種異常情況:如果線(xiàn)程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出SstackOverFlowError異常;如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryErro異常

本地方法棧

本地方法棧和虛擬機(jī)棧的作用是一樣的,只不過(guò)本地方法棧是虛擬機(jī)執(zhí)行java方法時(shí)開(kāi)辟的棧,而本地方法棧是虛擬機(jī)用到Native方法時(shí),開(kāi)辟的棧。

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

程序計(jì)數(shù)器是一塊較小的內(nèi)存,它可以看作是當(dāng)前線(xiàn)程的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取嚇一跳需要執(zhí)行的字節(jié)碼指令,分支,循環(huán),跳轉(zhuǎn),異常處理,線(xiàn)程恢復(fù)等基礎(chǔ)功能。

由于java虛擬機(jī)的多線(xiàn)程是通過(guò)線(xiàn)程輪流切換并分配處理器執(zhí)行時(shí)間的方式來(lái)實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器都只會(huì)執(zhí)行一條線(xiàn)程中的指令。因此,為了線(xiàn)程切換后能恢復(fù)到正確的執(zhí)行位置,每條線(xiàn)程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各線(xiàn)程指尖計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。

1.2 對(duì)象創(chuàng)建

了解了內(nèi)存的數(shù)據(jù)區(qū)域,我們可以進(jìn)一步了解對(duì)象是如何創(chuàng)建的了。這里先通過(guò)一張流程圖一窺java的對(duì)象創(chuàng)建過(guò)程
java內(nèi)存管理機(jī)制剖析(一)
可以看到,當(dāng)虛擬機(jī)遇到一條new指令時(shí),首先去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已經(jīng)加載。如果沒(méi)有,則執(zhí)行加載。

加載完成后,便會(huì)在堆中為對(duì)象分配內(nèi)存,JVM有兩種分配方式 ①指針碰撞,②空閑列表 ,下面我詳細(xì)講講這兩種分配方式。

—-指針碰撞

當(dāng)java堆中內(nèi)存是整齊的,所有用過(guò)的內(nèi)存都放一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針座位分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊摞動(dòng)一段與對(duì)象大小相等的距離,這種分配就叫指針碰撞

—-空閑列表

當(dāng)Java堆的內(nèi)存并不是完整的,已分配的內(nèi)存和空閑內(nèi)存相互交錯(cuò),JVM通過(guò)維護(hù)一個(gè)列表,記錄可用的內(nèi)存塊信息,當(dāng)分配操作發(fā)生時(shí),從列表中找到一個(gè)足夠大的內(nèi)存塊分配給對(duì)象實(shí)例,并更新列表上的記錄。這種分配方式稱(chēng)為空閑列表

當(dāng)JVM所采用的垃圾收集器帶有壓縮整理功能時(shí),java堆是規(guī)整的,這個(gè)時(shí)候會(huì)采用指針碰撞分配內(nèi)存,否則會(huì)采用空閑列表分配內(nèi)存。對(duì)象創(chuàng)建是一個(gè)非常頻繁的行為,進(jìn)行堆內(nèi)存分配時(shí)還需要考慮多線(xiàn)程并發(fā)問(wèn)題,可能出現(xiàn)正在給對(duì)象A分配內(nèi)存,指針或記錄還未更新,對(duì)象B又同時(shí)分配到原來(lái)的內(nèi)存,解決這個(gè)問(wèn)題有兩種方案:

  • 1、采用CAS保證數(shù)據(jù)更新操作的原子性;
  • 2、把內(nèi)存分配的行為按照線(xiàn)程進(jìn)行劃分,在不同的空間中進(jìn)行,每個(gè)線(xiàn)程在Java堆中預(yù)先分配一個(gè)內(nèi)存塊,稱(chēng)為本地線(xiàn)程分配緩沖(Thread Local Allocation Buffer, TLAB);
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI