【讀書會(huì)第十二期】深入理解Java虛擬機(jī) 第1章 JVM進(jìn)程如何進(jìn)行內(nèi)存管理

      網(wǎng)友投稿 664 2025-03-31

      【讀書會(huì)第十二期】深入理解Java虛擬機(jī) 第1章 JVM進(jìn)程如何進(jìn)行內(nèi)存管理


      【說在前面】:為啥要了解JVM的內(nèi)存區(qū)域?原來JAVA最引以為豪就是自動(dòng)內(nèi)存管理,跟C++手動(dòng)管理內(nèi)存,以及復(fù)雜的指針處理,用起來更加方便,所以本篇重點(diǎn)描述下JVM進(jìn)程是怎么管內(nèi)存的?

      本文的主要內(nèi)容有:

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

      虛擬機(jī)棧

      本地方法棧

      方法區(qū)

      堆區(qū)

      線程私有的內(nèi)存區(qū)域

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

      看圖我們先從簡單的說起,程序計(jì)數(shù)器是JVM所占內(nèi)存中較小的一塊內(nèi)存,在JVM里面用程序計(jì)數(shù)器記錄編譯過程字節(jié)碼文件所在的行,這個(gè)怎么記錄的呢?這個(gè)計(jì)數(shù)器說白了就是指向當(dāng)前行運(yùn)行的字節(jié)碼的頭指針,字節(jié)碼在執(zhí)行過程中程序計(jì)數(shù)器會(huì)記錄包括循環(huán),跳轉(zhuǎn),分支,以及異常處理,線程恢復(fù)等這些都要依賴計(jì)數(shù)器來完成;

      我們知道Java多線程是線程切換并分配處理器,所以任意時(shí)刻一個(gè)處理器只會(huì)處理一個(gè)線程的指令,為的是線程切換后能切換到正確執(zhí)行位置,所以每個(gè)線程都會(huì)有獨(dú)立的程序計(jì)數(shù)器;如果說線程它執(zhí)行加法這個(gè)計(jì)數(shù)器記錄的是虛擬字節(jié)碼的地址;那假如線程里面執(zhí)行的是本地方法,那么計(jì)數(shù)器為空,所以說計(jì)數(shù)器是唯一不會(huì)OOM的哦。

      2 虛擬機(jī)棧

      然后來看下虛擬機(jī)棧,它和程序計(jì)數(shù)器一樣都是線程私有的內(nèi)存區(qū)域,虛擬機(jī)棧的生命周期與線程相同。虛擬機(jī)棧里面存的是Java方法執(zhí)行流程,方法執(zhí)行都會(huì)創(chuàng)建一個(gè)棧幀來放局部變量表,操作數(shù)棧,動(dòng)態(tài)連接,方法返回地址等;每個(gè)方法調(diào)用以及結(jié)束,對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)入棧到出棧過程。局部變量表是比較為人所熟知的,也就是平常所說的“棧”,局部變量表所需的內(nèi)存空間在編譯期間分配完成,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在棧幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。

      這里如果出錯(cuò)虛擬機(jī)棧會(huì)發(fā)生兩種異常情況:

      StackOverflowError: 線程請(qǐng)求的棧大于虛擬機(jī)棧的深度,特別是方法的遞歸調(diào)用的時(shí)候拋出棧溢出異常

      OutOfMemoryError:虛擬機(jī)棧無法滿足線程所申請(qǐng)的空間需求,即使經(jīng)過動(dòng)態(tài)擴(kuò)展仍然無法滿足時(shí)拋出該異常

      看上面代碼,我寫了三個(gè)方法,A方法調(diào)用B方法,B方法調(diào)用C方法,然后C方法打印一行日志,然后我們debug,可以看到調(diào)試器有三個(gè)棧進(jìn)行壓棧,可以看到從上到下ABC一次入棧,那么CBA依次出棧,這個(gè)所謂棧幀;當(dāng)然這個(gè)棧幀存的數(shù)據(jù)如局部變量表,操作數(shù)棧以及動(dòng)態(tài)鏈接等,我們可以看這個(gè)局部變量在棧幀里變化是否符合我們預(yù)期;

      3 本地方法棧

      我們看下本地方法棧,與虛擬機(jī)棧功能是非常相似的,區(qū)別在于本地方法棧顧名思義執(zhí)行native方法,也就是我們常說的C++代碼,同樣本地方法棧也會(huì)拋出內(nèi)存與棧溢出異常。

      線程共享的區(qū)域

      1 方法區(qū)

      我們看下方法區(qū),它是線程共享區(qū)域,存儲(chǔ)JVM加載過的信息,比如常量,靜態(tài)變量,類信息以及編譯器編譯的代碼,以及符號(hào)引用;前面說了所有線程都能拿到方法區(qū)里面的數(shù)據(jù),需要注意的是方法區(qū)是線程安全的,比如兩個(gè)線程同時(shí)訪問方法區(qū)同一個(gè)類,而這個(gè)類還沒有加載進(jìn)JVM,所以只允許一個(gè)線程去訪問那個(gè)類,而其他線程必須等待。

      JDK在1.8之后,已經(jīng)取消永久代改為元空間,而元空間的元數(shù)據(jù)是放在本地內(nèi)存的,所以理論上系統(tǒng)能使用內(nèi)存有多大,那元空間就有多大;所以不會(huì)出現(xiàn)元空間內(nèi)存溢出異常,并且永久代調(diào)優(yōu)是很困難的,雖然說可以設(shè)置永久代大小,但是很難確定合適大小,因?yàn)槠渲杏绊懸蛩赜泻芏啵热珙悢?shù)量多少,動(dòng)態(tài)代理會(huì)生成class對(duì)象,常量數(shù)量的多少(String對(duì)象存在方法區(qū));永久代的數(shù)據(jù)位置也會(huì)隨著funGC發(fā)生移動(dòng),也就是進(jìn)行回收,這個(gè)是比較消耗性能的,虛擬機(jī)每種垃圾回收器都要特殊處理,永久代中元數(shù)據(jù)剝離出來,不僅實(shí)現(xiàn)元空間無縫管理,還可簡化funGC,給以后并發(fā)隔離,元數(shù)據(jù)等進(jìn)行優(yōu)化,

      2 堆區(qū)

      我們內(nèi)存區(qū)域最大一部分就是我們的堆,絕大部分對(duì)象都分在Eden區(qū),其中大多數(shù)對(duì)象都會(huì)被回收掉,Eden區(qū)域是連續(xù)內(nèi)存空間,因此再分配內(nèi)存的時(shí)候時(shí)間很短,當(dāng)Eden區(qū)域不足分配內(nèi)存不足時(shí)候就會(huì)yungc,把消亡的對(duì)象清理掉,并將復(fù)活對(duì)象復(fù)制到From Survivor與To Survivor區(qū),上面兩個(gè)區(qū)域總是有空的,因?yàn)閷?duì)象會(huì)進(jìn)行拷貝。當(dāng)Eden空間滿了,執(zhí)行mingc把消亡對(duì)象清理掉,將復(fù)活對(duì)象復(fù)制到From Survivor區(qū),然后清理Eden區(qū),把S區(qū)對(duì)象清理掉,回收年齡都會(huì)進(jìn)入老年代。如果對(duì)象在年輕帶存活足夠時(shí)間,而沒有清理掉也就是經(jīng)歷GC之后,存活下來也會(huì)進(jìn)入老年代,老年代空間一般比年輕代空間要大,能存放更多對(duì)象,在老年代發(fā)生GC次數(shù)比年輕代要少;當(dāng)老年代內(nèi)存不足時(shí),將執(zhí)行funGC,如果對(duì)象比較大,比如說大的字符串,數(shù)組;則將對(duì)象直接分配到老年代,由于絕大多數(shù)對(duì)象生命周期比較短,規(guī)定新生代占堆空間80%,S區(qū)域存活的對(duì)象超過內(nèi)存區(qū)域的10%,則將一部分對(duì)象分配到老年代,這里原則就是盡可能更多把對(duì)象放置到老年代;

      方法區(qū)同樣會(huì)拋出OutOfMemoryError異常。

      在方法區(qū)中有一部分區(qū)域用來存儲(chǔ)編譯期產(chǎn)生的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。這里需要說明一點(diǎn),常量并不是只能在編譯期產(chǎn)生,運(yùn)行期間也會(huì)產(chǎn)生新的常量并被在常量池中,如 String 類的 intern() 方法。

      舉個(gè)例子,創(chuàng)建了兩個(gè)對(duì)象,張三,李四,我們看下運(yùn)行時(shí)這兩個(gè)對(duì)象分布在哪里?person類有兩個(gè)成員變量num,age,還有一個(gè)成員方法eat(),這些字節(jié)碼存儲(chǔ)在方法區(qū),也就是我們1.7的永久代,編譯時(shí)候會(huì)創(chuàng)建一些棧幀,最后實(shí)例數(shù)據(jù)(張三,年齡以及字節(jié)碼的引用)需要在運(yùn)行時(shí)進(jìn)行訪問,這些數(shù)據(jù)是放在堆區(qū)。程序計(jì)數(shù)器運(yùn)行到i–,程序計(jì)數(shù)器進(jìn)行賦值,執(zhí)行到eat()就行 進(jìn)行壓棧,

      JVM遇到一條new指令時(shí),首先檢查這個(gè)參數(shù)是否正確,是否能在方法區(qū)的常量池定位到符號(hào)引用,這個(gè)符號(hào)引用指向class對(duì)象,并且檢查符號(hào)引用代表的類是否是已經(jīng)加載,解析或者初始化過,如果沒有必須先執(zhí)行相應(yīng)類加載過程,給新生對(duì)象分配空間的任務(wù)等同于把一塊確定大小內(nèi)存從java堆中劃分出來,然后對(duì)象進(jìn)行默認(rèn)值初始化,int是0,double是0.0,還有一些對(duì)象默認(rèn)值是空值,在進(jìn)行默認(rèn)值之后;如何找到對(duì)象的hashcode,數(shù)據(jù)信息,二級(jí)指針,這些存在對(duì)象里面;因?yàn)檫€沒有執(zhí)行構(gòu)造方法,沒有執(zhí)行init(), 現(xiàn)在執(zhí)行初始化方法,一個(gè)完整對(duì)象才創(chuàng)建成功。對(duì)象如何定位到方法,如何定位到字節(jié)碼的呢?對(duì)象頭的class Pointer的指針指向了方法區(qū)的已經(jīng)編譯后代碼。

      開發(fā)中,我們不會(huì)一直向鏈表中添加對(duì)象,如果是你的代碼里面對(duì)象沒有被移除掉,可能出現(xiàn)堆溢出異常;

      這就是堆外內(nèi)存溢出,因?yàn)檫@個(gè)unsafe()是比較危險(xiǎn)的,能直接分配一定內(nèi)存,下面用反射分別獲取1MB內(nèi)存,然后設(shè)置堆外內(nèi)存最大是10MB,最大虛擬機(jī)棧是20MB;

      邏輯內(nèi)存模型我們已經(jīng)看到了,那在java語言中,對(duì)象訪問是如何進(jìn)行的呢?對(duì)象訪問在Java語言中無處不在,是最普通的程序行為,這其中涉及到的java棧、java堆、方法區(qū)這三個(gè)重要的內(nèi)存區(qū)域,來看如下代碼:

      public class JVMMemoryTest { /** * jvm自動(dòng)尋找main方法,執(zhí)行main方法,此時(shí)虛擬機(jī)棧中有一個(gè)代表main方法的棧幀入棧, * 只有main方法執(zhí)行完畢后才出棧 * @param args */ public static void main(String[] args) { /** * 1.student是對(duì)象的引用,所有會(huì)保存在棧幀的局部變量里 * 2.創(chuàng)建Student的時(shí)候,首先進(jìn)行類的加載工作,類只會(huì)加載一次, * 將類的類型信息數(shù)據(jù)加載到j(luò)vm的方法區(qū)中,如果之前加載過了,那么就不會(huì)再加載了 * 3.類的加載其實(shí)就是將.class文件加載進(jìn)虛擬機(jī)內(nèi)存中,在加載的時(shí)候,在java堆中生成對(duì)應(yīng)的Class對(duì)象, * 最后生成一個(gè)Student對(duì)象在堆中 */ Student student=new Student(18,"tom","007"); /** * 聲明定義一個(gè)int類型的變量a,因?yàn)閍是基本數(shù)據(jù)類型,所以在棧中直接分配一個(gè)內(nèi)存保存這個(gè)變量 */ int a=9; int b=10; /** * 執(zhí)行study方法,在棧中加入一個(gè)棧幀,執(zhí)行完畢后這個(gè)棧幀將出棧 * 在study方法中,有兩個(gè)int類型的局部變量,是保存在棧幀的局部變量內(nèi)存區(qū)中的 */ student.study(a,b); } //靜態(tài)內(nèi)部類 public static class Student extends Person implements IStudyable { private static int cnt = 5; static { cnt++; } private String sid; public Student(int age, String name, String sid) { super(age, name); this.sid = sid; } public void run() { System.out.println("run()..."); } public int study(int a, int b) { int c = 10; int d = 20; return a + b * c - d; } public static int getCnt() { return cnt; } } //父類 static class Person { private String name; private int age; public Person(int age, String name) { this.age = age; this.name = name; } public void run() { } } //接口 interface IStudyable { public int study(int a, int b); } }

      上面例子展示了一個(gè)簡單的代碼,JVM是如何分配內(nèi)存的,還有需要說明的就是new Student的時(shí)候,在java堆里面形成一塊存儲(chǔ)了Student類型的所有實(shí)例數(shù)值(instance Data,對(duì)象中的各個(gè)實(shí)例字段數(shù)據(jù))的結(jié)構(gòu)化內(nèi)存,這塊內(nèi)存的長度是不固定的。另外,在java堆中還必須包含能查找到此對(duì)象類型數(shù)據(jù)(如對(duì)象類型、父類、實(shí)現(xiàn)的接口、方法等)的地址信息,這些類型數(shù)據(jù)是存儲(chǔ)在方法區(qū)中的

      不同的虛擬機(jī)實(shí)現(xiàn)對(duì)象的訪問方式有所不同,主流的方式有兩種:

      使用句柄和直接指針

      如果使用句柄訪問方式,java堆中將會(huì)劃出一塊內(nèi)存來作為句柄池,reference中存儲(chǔ)的是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息,如圖所示:

      如果使用指針直接訪問的方式,java對(duì)象的布局中就必須放置有訪問類型數(shù)據(jù)的指針,而reference中直接存儲(chǔ)的是對(duì)象的地址,如圖所示:

      這兩種訪問方式各有優(yōu)勢(shì),其中使用直接指針訪問方式最大的好處就是訪問速度快,可節(jié)省程序的執(zhí)行成本哦。

      Java JVM 云享.書庫 云社區(qū)

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:企業(yè)數(shù)字化轉(zhuǎn)型的六個(gè)“法門”
      下一篇:excel雙縱坐標(biāo)怎么做?(excel中雙縱坐標(biāo)怎么畫)
      相關(guān)文章
      亚洲国产精品久久久久秋霞小| 亚洲春黄在线观看| 亚洲啪啪免费视频| 久久精品国产精品亚洲精品| 国产乱辈通伦影片在线播放亚洲| 亚洲人成影院在线观看| 亚洲国产成人久久笫一页| 亚洲?V乱码久久精品蜜桃| 男人的天堂亚洲一区二区三区 | 中文字幕亚洲精品无码| 亚洲天堂2017无码中文| 亚洲人成网站色在线观看| 亚洲精品午夜国产va久久| 丁香婷婷亚洲六月综合色| 国产99在线|亚洲| 亚洲精品无码久久久久牙蜜区| 亚洲人成色4444在线观看| 亚洲精品无码专区| 另类专区另类专区亚洲| 亚洲高清无码在线观看| 亚洲乱码中文字幕综合234| 国产亚洲精品线观看动态图| 亚洲日韩精品A∨片无码| 亚洲韩国精品无码一区二区三区| 亚洲成AV人片一区二区| 亚洲va在线va天堂va不卡下载 | 亚洲欧美国产日韩av野草社区| 亚洲欧洲精品成人久久曰| 无码亚洲成a人在线观看| 亚洲Av无码乱码在线znlu| 亚洲精品无码激情AV| 亚洲伊人久久精品影院| 国产av无码专区亚洲av桃花庵| 亚洲a一级免费视频| 亚洲av永久无码精品三区在线4| 最新亚洲春色Av无码专区| 精品久久久久亚洲| 久久夜色精品国产亚洲av| 亚洲日韩乱码中文无码蜜桃臀网站 | 国产成人精品久久亚洲高清不卡 | 亚洲无线码一区二区三区|