日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

走进JVM【二】理解JVM内存区域

發(fā)布時(shí)間:2023/12/2 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 走进JVM【二】理解JVM内存区域 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

引言

對(duì)于C++程序員,內(nèi)存分配與回收的處理一直是令人頭疼的問(wèn)題。Java由于自身的自動(dòng)內(nèi)存管理機(jī)制,使得管理內(nèi)存變得非常輕松,不容易出現(xiàn)內(nèi)存泄漏,溢出的問(wèn)題。

不容易不代表不會(huì)出現(xiàn)問(wèn)題,一旦內(nèi)存泄漏或溢出的情況發(fā)生,調(diào)試起來(lái)會(huì)變得非常困難。這就要求我們對(duì)虛擬機(jī)的內(nèi)存區(qū)域有深入的理解。最終能夠判斷內(nèi)存方面的異常發(fā)生時(shí),具體在JVM中的位置。

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

JVM運(yùn)行時(shí),首先需要類(lèi)加載器(ClassLoader)?加載所需類(lèi)的字節(jié)碼,加載完畢交由執(zhí)行引擎執(zhí)行,執(zhí)行過(guò)程中需要一段空間來(lái)存儲(chǔ)數(shù)據(jù)(類(lèi)比CPU與主存)。這段內(nèi)存空間的分配和釋放過(guò)程正是我們所關(guān)心的,稱(chēng)為運(yùn)行時(shí)數(shù)據(jù)區(qū)。

對(duì)于CS相關(guān)從業(yè)者,深入理解操作系統(tǒng)的內(nèi)存的層次結(jié)構(gòu),分配與垃圾收集過(guò)程都是大有裨益的。同理,欲定位內(nèi)存問(wèn)題的出現(xiàn)區(qū)域,必須剖析運(yùn)行時(shí)數(shù)據(jù)區(qū)。

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

如上圖所示,運(yùn)行時(shí)數(shù)據(jù)區(qū)包括:程序計(jì)數(shù)器(即PC寄存器),Java 虛擬機(jī)棧(VM Stack),Java 堆(Heap),方法區(qū)(Method Area),本地方法棧(Native Method Stack)。下面帶領(lǐng)大家深入理解各個(gè)數(shù)據(jù)區(qū)域。

JVM實(shí)際上就是一臺(tái)虛擬的計(jì)算機(jī),目的是為了實(shí)現(xiàn)"一次編譯,處處執(zhí)行"。所以,在理解運(yùn)行時(shí)數(shù)據(jù)區(qū)時(shí),完全可以與操作系統(tǒng)系統(tǒng) 內(nèi)存,寄存器類(lèi)比學(xué)習(xí)。

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

每條虛擬機(jī)中的線(xiàn)程都有自己的寄存器,稱(chēng)之為程序計(jì)數(shù)器(PC)。為了保證線(xiàn)程之間的獨(dú)立性,因而PC內(nèi)的空間是線(xiàn)程私有的。

  • 線(xiàn)程私有只能本線(xiàn)程訪(fǎng)問(wèn)的區(qū)域,其他線(xiàn)程無(wú)權(quán)訪(fǎng)問(wèn)。

程序計(jì)數(shù)器的作用

虛擬機(jī)中的多線(xiàn)程通過(guò)線(xiàn)程輪轉(zhuǎn)調(diào)度,為每條線(xiàn)程分配時(shí)間片來(lái)實(shí)現(xiàn)并發(fā)執(zhí)行。同一時(shí)刻,處理機(jī)只能執(zhí)行一條線(xiàn)程。當(dāng)切換到另外一條線(xiàn)程時(shí),若不保存當(dāng)前未執(zhí)行完線(xiàn)程的執(zhí)行位置,下次處理機(jī)再執(zhí)行這條線(xiàn)程時(shí),又要重新開(kāi)始執(zhí)行。這種情況顯然是不能容忍的。

引入程序計(jì)數(shù)器的目的,就是為了記錄線(xiàn)程的執(zhí)行情況,便于下次切換后進(jìn)行線(xiàn)程恢復(fù)。

程序計(jì)數(shù)器的機(jī)制

如何記錄線(xiàn)程的執(zhí)行情況? 其實(shí)也并不復(fù)雜,只需要記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。如果運(yùn)行的是Native(本地)方法,計(jì)數(shù)器的值為Undefined。

程序計(jì)數(shù)器是唯一沒(méi)有OutOfMemoryError異常的區(qū)域。

Java 虛擬機(jī)棧

每個(gè)Java方法執(zhí)行時(shí),需要分配內(nèi)存空間來(lái)存儲(chǔ)局部變量表操作數(shù)棧動(dòng)態(tài)鏈接方法出口等信息。將這部分內(nèi)存稱(chēng)之為棧幀(Stack Frame)。虛擬機(jī)棧用于存儲(chǔ)棧幀,是Java方法執(zhí)行的內(nèi)存模型

顯然我們需要為每個(gè)執(zhí)行的方法分配??臻g,因此Java虛擬機(jī)棧也是線(xiàn)程私有的。

虛擬機(jī)棧的作用

虛擬機(jī)棧記錄Java方法執(zhí)行的過(guò)程。每個(gè)方法開(kāi)始執(zhí)行時(shí),為之創(chuàng)建一個(gè)棧幀記錄信息;方法執(zhí)行到完成的過(guò)程,對(duì)應(yīng)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。

局部變量表

局部變量表是棧幀中的重要部分。存放編譯期定義的基本數(shù)據(jù)類(lèi)型,?對(duì)象引用(相當(dāng)于對(duì)象地址),及returnAddress類(lèi)型(字節(jié)碼指令地址)。

局部變量表空間在編譯期間分配,執(zhí)行方法的過(guò)程中不會(huì)改變其大小。

異常

  • 當(dāng)線(xiàn)程請(qǐng)求的棧深度大于所允許的深度,拋出StackOverflowError異常。
  • 長(zhǎng)度不夠時(shí),虛擬機(jī)??蛇M(jìn)行動(dòng)態(tài)擴(kuò)展,申請(qǐng)內(nèi)存。若無(wú)法申請(qǐng)到足夠的內(nèi)存,拋出OutOfMemoryError異常。
  • 本地方法棧

    本地方法棧與虛擬機(jī)棧類(lèi)似,區(qū)別是虛擬機(jī)棧記錄執(zhí)行的Java方法,本地方法棧則記錄Native方法。

    本地方法棧同樣會(huì)拋出StackOverflowErrorOutOfMemoryError異常。

    Java 堆

    Java堆用于存儲(chǔ)對(duì)象實(shí)例,為所有對(duì)象分配內(nèi)存空間。

    所有對(duì)象實(shí)例都要在堆上分配空間,因此Java堆是所有線(xiàn)程共享區(qū)域。對(duì)象的生命周期結(jié)束后,Java堆還要負(fù)責(zé)內(nèi)存回收,因此Java堆也常被稱(chēng)之為GC堆(Garbage Collected Heap)。

    內(nèi)存模型

    從內(nèi)存回收的角度,Java堆可以分為新生代(Young Generation)與老生代(Old Generation)。這種劃分的方式,是為了更好的回收內(nèi)存(老生代內(nèi)存會(huì)被優(yōu)先回收)。

    如圖,新生代還可以分為Eden空間From Survivor空間、To Survivor空間。

    永久代(Permanent Generation)用于存儲(chǔ)靜態(tài)類(lèi)型數(shù)據(jù),與垃圾收集器關(guān)系不大。

    注意:本圖展示的是JVM堆的內(nèi)存模型,JVM堆內(nèi)存包括Java堆區(qū)域?和?永久代區(qū)域。因此,永久代不屬于Java堆。

    異常

    Java堆同樣可擴(kuò)展(-Xmx與-Xms參數(shù))。若堆中內(nèi)存已無(wú)法為對(duì)象實(shí)例分配且無(wú)法再擴(kuò)展,拋出OutOfMemoryError異常。

    方法區(qū)

    方法區(qū)存儲(chǔ)類(lèi)信息、常量、靜態(tài)變量等數(shù)據(jù),是線(xiàn)程共享的區(qū)域。為與Java堆區(qū)分,方法區(qū)還有一個(gè)別名Non-Heap(非堆)。

    方法區(qū)≠永久代

    方法區(qū)就是永久代?并非如此。

    HotSpot虛擬機(jī)選擇用永久代來(lái)實(shí)現(xiàn)方法區(qū),從而省去了為方法區(qū)編寫(xiě)內(nèi)存管理代碼的工作。這只是一種實(shí)現(xiàn)方式,其他虛擬機(jī)(BEA JRockit,IBM J9)都不存在永久代這一概念。

    通過(guò)永久代來(lái)實(shí)現(xiàn)方法區(qū)容易造成內(nèi)存溢出,未來(lái)也可能會(huì)被替代。

    在虛擬機(jī)規(guī)范中,方法區(qū)的實(shí)現(xiàn)沒(méi)有明確的規(guī)定,因此不能將方法區(qū)等同于永久代。

    異常

    當(dāng)方法區(qū)無(wú)法滿(mǎn)足內(nèi)存分配的需要時(shí),拋出OutOfMemoryError異常。

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

    運(yùn)行時(shí)常量池(Runtime Constant Pool)用于存放編譯期生成的各種字面量和符號(hào)引用。

    運(yùn)行時(shí)常量池具備動(dòng)態(tài)性,使得運(yùn)行期間也可將新的常量放入池中。例如String類(lèi)的intern()?方法。

    package intern;public class Main1 {public static void main(String[] args) {String s0= "I'm coding"; String s1=new String("I'm coding"); String s2=new String("I'm coding"); system.out.println( s0==s1 ); System.out.println( s0==s1.intern()); s2=s2.intern(); System.out.println( s0==s2 ); } }

    輸出結(jié)果

    false true true

    本例中,s0直接保存在常量池,s1與s2的對(duì)象實(shí)例存儲(chǔ)在Java堆中。==直接比較對(duì)象的hashCode,因此第一行輸出false。s1.intern()方法返回s1在常量池中的引用,沒(méi)有則創(chuàng)建。
    s1存放的字符串已經(jīng)在常量池中存在,直接返回s0的引用,第二行輸出true
    同理,s2接收了s2.intern()的返回值,字符串值與s0相同,第三行輸出true

    運(yùn)行時(shí)常量池是方法區(qū)的一部分,因此受方法區(qū)內(nèi)存的限制。當(dāng)無(wú)法申請(qǐng)到內(nèi)存時(shí),拋出OutOfMemoryError異常。

    總結(jié)

    對(duì)于JVM的內(nèi)存管理, 最重要的還是與OS內(nèi)存管理知識(shí)進(jìn)行類(lèi)比以及結(jié)合實(shí)踐來(lái)學(xué)習(xí)。理解JVM內(nèi)存區(qū)域的目的也是為了在工程中出現(xiàn)內(nèi)存相關(guān)異常時(shí)能夠準(zhǔn)確的定位所在區(qū)域,及時(shí)處理。

    后續(xù)我們將在本文的基礎(chǔ)上來(lái)理解對(duì)象的創(chuàng)建過(guò)程以及OutOfMemoryError異常。

    總結(jié)

    以上是生活随笔為你收集整理的走进JVM【二】理解JVM内存区域的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。