<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          全面解析JVM,超詳細(xì)!

          共 8143字,需瀏覽 17分鐘

           ·

          2021-02-04 10:01

          點(diǎn)擊藍(lán)色“程序員的時(shí)光?”關(guān)注我?,標(biāo)注“星標(biāo)”,及時(shí)閱讀最新技術(shù)文章

          寫(xiě)在前面:

          小伙伴兒們,大家好!今天來(lái)學(xué)習(xí)Java虛擬機(jī)相關(guān)內(nèi)容,作為面試必問(wèn)的知識(shí)點(diǎn),來(lái)深入了解一波!

          思維導(dǎo)圖:

          JVM思維導(dǎo)圖

          1,JVM是什么?

          1.1,概述

          JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫(xiě),JVM是一種用于計(jì)算設(shè)備的規(guī)范。引入Java虛擬機(jī)后,Java語(yǔ)言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語(yǔ)言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。任何平臺(tái)只要裝有針對(duì)于該平臺(tái)的Java虛擬機(jī),字節(jié)碼文件(.class)就可以在該平臺(tái)上運(yùn)行。這就是“一次編譯,多次運(yùn)行”。

          所謂java能實(shí)現(xiàn)跨平臺(tái),是由在不同平臺(tái)上運(yùn)行不同的虛擬機(jī)決定的,因此java文件的執(zhí)行不直接在操作系統(tǒng)上執(zhí)行,而是通過(guò)jvm虛擬機(jī)執(zhí)行,我們可以從這張圖看到,JVM并沒(méi)有直接與硬件打交道,而是與操作系統(tǒng)交互用以執(zhí)行java程序。

          跨平臺(tái)

          1.2,JVM運(yùn)行流程

          JVM運(yùn)行流程

          這個(gè)是JVM的組成圖,由四個(gè)部分組成:

          • 類加載器

            類加載器的作用是加載類文件到內(nèi)存。比如我們執(zhí)行一個(gè).java程序的文件,首先使用javac命令進(jìn)行編譯,生成.class文件。然后我們需要用類加載器將字節(jié)碼文件加載到內(nèi)存中去,通過(guò)jvm后續(xù)的模塊進(jìn)行加載執(zhí)行程序。至于是否能夠執(zhí)行,則由執(zhí)行引擎負(fù)責(zé)。

          • 執(zhí)行引擎

            執(zhí)行引擎也叫解釋器,負(fù)責(zé)解釋命令,提交操作系統(tǒng)執(zhí)行。

          • 本地接口

            它的作用是融合不同的編程語(yǔ)言為Java所用,目前該方法使用的是越來(lái)越少了,除非是與硬件有關(guān)的應(yīng)用,比如通過(guò)Java程序驅(qū)動(dòng)打印機(jī),或者Java系統(tǒng)管理生產(chǎn)設(shè)備。

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

            運(yùn)行數(shù)據(jù)區(qū)是整個(gè)JVM的重點(diǎn)。我們所有寫(xiě)的程序都被加載到這里,之后才開(kāi)始運(yùn)行,Java生態(tài)系統(tǒng)如此的繁榮,得益于該區(qū)域的優(yōu)良自治。整個(gè)JVM框架由加載器加載文件,然后執(zhí)行器在內(nèi)存中處理數(shù)據(jù),需要與異構(gòu)系統(tǒng)交互是可以通過(guò)本地接口進(jìn)行!

          2,JVM的內(nèi)存區(qū)域

          內(nèi)存區(qū)域也就是上面的運(yùn)行時(shí)數(shù)據(jù)區(qū)。對(duì)于從事C或者C++的程序員來(lái)說(shuō),必須對(duì)每個(gè)對(duì)象的整個(gè)生命周期負(fù)責(zé)。但是對(duì)java程序員來(lái)說(shuō),在jvm的自動(dòng)內(nèi)存管理機(jī)制下,不需要為每一個(gè)對(duì)象去寫(xiě)delete或者free代碼,不容易出現(xiàn)內(nèi)存泄漏或內(nèi)存溢出的問(wèn)題。但正因?yàn)閖ava程序員將內(nèi)存管理權(quán)力交給了內(nèi)存管理機(jī)制,所以一旦出現(xiàn)內(nèi)存泄漏或者內(nèi)存溢出的問(wèn)題,在對(duì)jvm內(nèi)存結(jié)構(gòu)不清楚的情況下,排查錯(cuò)誤將會(huì)成為一項(xiàng)非常復(fù)雜且困難的工作。

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

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

          2.1,程序計(jì)算器

          程序計(jì)數(shù)器是一小塊的內(nèi)存區(qū)域,可以看做當(dāng)前線程執(zhí)行字節(jié)碼的行號(hào)指示器,在虛擬機(jī)的概念模型里,**字節(jié)碼解釋工作就是通過(guò)改變這個(gè)程序計(jì)數(shù)器的值來(lái)選取下一個(gè)要執(zhí)行的字節(jié)碼指令。**比如分支控制,循環(huán)控制,跳轉(zhuǎn),異常等操作,線程恢復(fù)等功能都是通過(guò)這個(gè)計(jì)數(shù)器來(lái)完成。

          由于jvm的多線程是通過(guò)線程的輪流切換并分配處理器執(zhí)行時(shí)間來(lái)實(shí)現(xiàn)的。因此,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來(lái)說(shuō)是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令。因此,為了線程切換后能回到正確的執(zhí)行位置,每條線程都需要自己獨(dú)有的程序計(jì)數(shù)器,多條線程計(jì)數(shù)器之間互不影響,獨(dú)立存儲(chǔ)。我們稱這類內(nèi)存區(qū)域?yàn)榫€程私有的內(nèi)存區(qū)域。

          如果線程執(zhí)行的是Java方法時(shí),程序計(jì)數(shù)器記錄的是 Java 虛擬機(jī)正在執(zhí)行的字節(jié)碼指令的地址,而在線程執(zhí)行 Native 方法時(shí),程序計(jì)數(shù)器為空,因?yàn)榇藭r(shí) Java 虛擬機(jī)調(diào)用是和操作系統(tǒng)相關(guān)的接口,接口的實(shí)現(xiàn)不是 Java 語(yǔ)言,而是 C語(yǔ)言和 C++。

          程序計(jì)數(shù)器是唯一一個(gè)在Java虛擬機(jī)中不會(huì)出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域,它的生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而結(jié)束。

          2.2,Java虛擬機(jī)棧

          與程序計(jì)數(shù)器一致,Java虛擬機(jī)棧也是線程私有的,生命周期與線程相同。虛擬機(jī)棧描述的是Java方法的執(zhí)行內(nèi)存模型,每個(gè)方法在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀(用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈棧、方法出口等信息)。每一個(gè)方法從執(zhí)行到結(jié)束的過(guò)程,就對(duì)應(yīng)一個(gè)棧幀從入棧到出棧的過(guò)程。

          Java內(nèi)存可以粗糙地分為堆內(nèi)存(Heap)棧內(nèi)存(Stack),當(dāng)然Java內(nèi)存區(qū)域的劃分實(shí)際上遠(yuǎn)比這復(fù)雜,我們現(xiàn)在所說(shuō)的Java虛擬機(jī)棧就是這里的棧內(nèi)存,或者說(shuō)是虛擬機(jī)棧中局部變量表部分

          局部變量表存放了編譯器可知的四類八種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double),對(duì)象引用(reference類型,它不同于對(duì)象本身,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苁侵赶蛞粋€(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置)。

          Java虛擬機(jī)會(huì)出現(xiàn)兩種異常狀況:

          如果線程在棧中申請(qǐng)的深度大于虛擬機(jī)所允許的深度,將出現(xiàn)StackOverFlowError異常; 如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,且擴(kuò)展無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。

          2.3,本地方法棧

          本地方法棧與虛擬機(jī)棧的作用非常類似,區(qū)別是:虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù),在 HotSpot 虛擬機(jī)中直接將虛擬機(jī)棧和本地方法棧合二為一。

          與Java虛擬機(jī)棧一樣,本地方法棧在執(zhí)行的時(shí)候也會(huì)創(chuàng)建一個(gè)棧幀(用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈棧、方法出口等信息)。也會(huì)拋出StackOverFlowError異常和OutOfMemoryError異常。

          2.4,Java堆

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

          Java堆是垃圾收集器管理的主要區(qū)域,因此也被稱作GC堆(Garbage Collected Heap)。從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都采用分代垃圾收集算法,所以Java堆還可以細(xì)分為:新生代和老年代。**進(jìn)一步劃分的目的是更好地回收內(nèi)存,或者更快地分配內(nèi)存。**根據(jù)JVM的規(guī)范規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間,只要邏輯上是連續(xù)的即可。如果在堆中沒(méi)有完成內(nèi)存分配,且堆也沒(méi)有可擴(kuò)展的內(nèi)存空間,則會(huì)拋出OutOfMemoryError異常。

          2.5,方法區(qū)

          方法區(qū)與 Java 堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做 Non-Heap(非堆),目的應(yīng)該是與 Java 堆區(qū)分開(kāi)來(lái)。

          Java虛擬機(jī)相對(duì)而言對(duì)方法區(qū)的限制非常寬松,除了和堆一樣不需要連續(xù)的空間可以選擇固定大小或者可擴(kuò)展之外,還可以選擇不實(shí)現(xiàn)垃圾回收。相對(duì)而言,垃圾回收在這個(gè)區(qū)域算比較少見(jiàn)了,但并非數(shù)據(jù)進(jìn)入方法區(qū)以后就可以實(shí)現(xiàn)永久存活了,這個(gè)區(qū)域的回收目標(biāo)主要是常量池的回收和對(duì)類型的卸載,一般來(lái)說(shuō),這個(gè)區(qū)域的回收成績(jī)是比較難以讓人滿意的。尤其是類型的卸載,條件相當(dāng)苛刻。根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配時(shí),將拋出OutOfMemoryError異常。

          我們?cè)谶@里舉一個(gè)簡(jiǎn)單例子來(lái)看看,看看上述的哪些信息會(huì)存放上方法區(qū)中;

          靜態(tài)變量和常量,在編譯期間就放在方法區(qū)中;

          //靜態(tài)變量,在編譯期間存放在方法區(qū)
          private?static?int?num=10;
          //常量,在編譯期間存放在方法區(qū)
          private?final?String?name="boy";

          我們先來(lái)看看new String時(shí)堆中的變化;

          String?s1="hello";
          String?s2=new?String("hello");
          String?s3=new?String("hello");
          System.out.println(s1==s3);??//?false
          System.out.println(s2==s3);??//?false

          這個(gè)輸出的結(jié)果肯定是false,采用new的時(shí)候會(huì)在堆內(nèi)存開(kāi)辟一塊空間存放hello對(duì)象,雖然s2和s3指向的內(nèi)容相同,但是棧種存放的地址不同,所以是不相等的。

          棧和堆的變化

          對(duì)于引用類型來(lái)說(shuō),"=="指的是地址值的比較。

          雙引號(hào)直接寫(xiě)的字符串是在常量池之中,而new的對(duì)象則不在池之中。

          再來(lái)看看運(yùn)行期間添加進(jìn)常量池的;

          String?s2=new?String("hello");
          String?s3=new?String("hello");
          //在運(yùn)行過(guò)程中添加進(jìn)常量池中
          System.out.println(s2.intern()==s3.intern());
          棧和堆的變化

          如果常量池中存在當(dāng)前字符串,那么直接返回常量池中該對(duì)象的引用

          如果常量池中沒(méi)有此字符串, 會(huì)將此字符串引用保存到常量池中后, 再直接返回該字符串的引用

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

          運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有常量池信息(用于存放編譯期生成的各種字面量和符號(hào)引用)。既然運(yùn)行時(shí)常量池時(shí)方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError 異常。

          2.7,直接內(nèi)存

          直接內(nèi)存并不屬于Jvm運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,但是這部分內(nèi)存區(qū)域被頻繁的調(diào)用,也可能發(fā)生OutOfMemoryError異常。顯然本機(jī)的直接內(nèi)存不會(huì)受到Java堆分配內(nèi)存的影響,但是既然是內(nèi)存,肯定要受到本機(jī)總內(nèi)存大小以及處理器尋址空間的限制。服務(wù)器管理員在配置虛擬機(jī)參數(shù)時(shí),會(huì)根據(jù)實(shí)際內(nèi)存設(shè)置-Xmx等參數(shù)信息,但經(jīng)常忽略直接內(nèi)存。使得各個(gè)區(qū)域的內(nèi)存總和大于物理內(nèi)存限制,從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMemoryError異常。

          3,Java對(duì)象的創(chuàng)建過(guò)程

          下面這張圖就是Java對(duì)象創(chuàng)建的過(guò)程,總共來(lái)說(shuō)分為五部分;

          Java對(duì)象創(chuàng)建流程

          3.1,類加載過(guò)程

          虛擬機(jī)遇到一條 new 指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過(guò)、解析和初始化過(guò)。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類加載過(guò)程。

          3.2,分配內(nèi)存

          在類加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來(lái)。分配方式“指針碰撞”“空閑列表” 兩種,選擇哪種分配方式由 Java 堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定

          指針碰撞:

          • 場(chǎng)景:Java堆中內(nèi)存是絕對(duì)規(guī)整的;
          • 原理:所有用過(guò)的內(nèi)存都放在一邊,空閑的內(nèi)存放在另外一邊,中間放一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存時(shí)只需要把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離就可以了;
          • GC收集器:Serial、ParNew等帶Compact過(guò)程的收集器。

          空閑列表:

          • 場(chǎng)景:Java堆中內(nèi)存不是規(guī)整的;
          • 原理:虛擬機(jī)會(huì)維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄;
          • GC收集器:CMS基于Mark-Sweep算法的收集器。

          內(nèi)存分配并發(fā)的問(wèn)題

          在創(chuàng)建對(duì)象的時(shí)候還需要考慮的一個(gè)問(wèn)題就是在并發(fā)情況下,線程是否安全的問(wèn)題。因?yàn)閯?chuàng)建對(duì)象在虛擬機(jī)中是非常頻繁的行為,可能出現(xiàn)正在給對(duì)象A分配內(nèi)存,指針還沒(méi)來(lái)得及修改,對(duì)象B又同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存的情況。因此必須要保證線程安全,解決這個(gè)問(wèn)題有兩種方案:

          • **CAS以及失敗重試(比較和交換機(jī)制):**對(duì)分配內(nèi)存空間的操作進(jìn)行同步處理——實(shí)際上虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性。CAS操作需要輸入兩個(gè)數(shù)值,一個(gè)舊值(操作前期望的值)和一個(gè)新值,在操作期間先比較舊值有沒(méi)有發(fā)送變化,如果沒(méi)有變化,才交換成新值,否則不進(jìn)行交換。
          • **TLAB(分配緩沖):**把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊私有內(nèi)存,也就是本地線程分配緩沖。TLAB的目的是在為新對(duì)象分配內(nèi)存空間時(shí),讓每個(gè)Java應(yīng)用線程能在使用自己專屬的分配指針來(lái)分配空間,減少同步開(kāi)銷(xiāo)。

          3.3,初始化零值

          內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

          3.4,設(shè)置對(duì)象頭

          初始化零值完成之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是那個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希嗎、對(duì)象的 GC 分代年齡等信息。這些信息存放在對(duì)象頭中。 另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。

          3.5,執(zhí)行Init方法

          在上面工作都完成之后,從虛擬機(jī)的視角來(lái)看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來(lái)看,對(duì)象創(chuàng)建才剛開(kāi)始, 方法還沒(méi)有執(zhí)行,所有的字段都還為零。所以一般來(lái)說(shuō),執(zhí)行 new 指令之后會(huì)接著執(zhí)行 方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來(lái)。

          4,對(duì)象的訪問(wèn)定位

          建立對(duì)象就是為了使用對(duì)象,我們的Java程序通過(guò)棧上的 reference 數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。對(duì)象的訪問(wèn)方式由虛擬機(jī)實(shí)現(xiàn)而定,目前主流的訪問(wèn)方式有使用句柄直接指針兩種。

          4.1,使用句柄

          如果使用句柄的話,那么Java堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference 中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。如圖所示:

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

          4.2,直接指針

          如果使用直接指針訪問(wèn),那么 Java 堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息,而reference 中存儲(chǔ)的直接就是對(duì)象的地址。如圖所示:

          通過(guò)直接指針訪問(wèn)對(duì)象

          這兩種對(duì)象訪問(wèn)方式各有優(yōu)勢(shì),**使用句柄來(lái)訪問(wèn)的最大好處就是reference 中存儲(chǔ)的是穩(wěn)定的句柄地址,**在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而 reference 本身不需要修改。**使用直接指針訪問(wèn)方式最大的好處就是速度更快,**它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)。由于對(duì)象的訪問(wèn)在Java中非常頻繁,因此這類開(kāi)銷(xiāo)積少成多后也是一項(xiàng)非常樂(lè)觀的執(zhí)行成本。

          5,OutOfMemoryError(內(nèi)存溢出)異常

          在Java虛擬機(jī)規(guī)范的描述中,除了程序計(jì)算器之外,**虛擬機(jī)內(nèi)存的其他幾個(gè)運(yùn)行時(shí)區(qū)域都有發(fā)生OutOfMemoryError異常的可能。**現(xiàn)在我們通過(guò)兩個(gè)實(shí)例來(lái)驗(yàn)證異常發(fā)生的場(chǎng)景,也會(huì)初步介紹幾個(gè)與內(nèi)存相關(guān)的最基本的虛擬機(jī)參數(shù)。

          5.1,堆內(nèi)存異常

          我們來(lái)演示一下堆內(nèi)存的異常:

          /**
          ?*?@author?公眾號(hào):程序員的時(shí)光
          ?*?@create?2020-11-23?08:54
          ?*?@description
          ?*/

          public?class?HeapOOM?{
          ????public?static?void?main(String[]?args)?{
          ????????//測(cè)試堆內(nèi)存異常
          ????????List?heapOOMList=new?ArrayList<>();
          ????????//這里只添加一個(gè)對(duì)象,不會(huì)發(fā)生異常
          ????????heapOOMList.add(new?HeapOOM());
          ????????//添加進(jìn)死循環(huán),不斷地new對(duì)象,堆內(nèi)存已經(jīng)耗盡
          ????????while?(true)?{
          ????????????heapOOMList.add(new?HeapOOM());
          ????????}
          ????}
          }

          在運(yùn)行這個(gè)程序之前,我們先要設(shè)置Java虛擬機(jī)的參數(shù)。由于IDEA默認(rèn)設(shè)置的堆內(nèi)存很大,所以我們需要單個(gè)配置;點(diǎn)擊Run >> Edit Configurations,然后就開(kāi)始配置,如下,初始化堆內(nèi)存和最大堆內(nèi)存都設(shè)置為10m,看看上面的死循環(huán)能否在10m內(nèi)存中完成;

          設(shè)置堆內(nèi)存參數(shù)

          我們來(lái)看運(yùn)行結(jié)果:

          運(yùn)行結(jié)果

          可以看到堆內(nèi)存發(fā)生異常,上面的死循環(huán)中我們不斷地new對(duì)象,導(dǎo)致堆內(nèi)存已經(jīng)耗盡,無(wú)法為新生的對(duì)象分配內(nèi)存,從而發(fā)生異常

          5.2,棧內(nèi)存異常

          再來(lái)看看棧內(nèi)存異常:

          /**
          ?*?@author?公眾號(hào):程序員的時(shí)光
          ?*?@create?2020-11-23?09:14
          ?*?@description
          ?*/

          public?class?StackOOM?{
          ????public?static?void?main(String[]?args)?{
          ????????test();
          ????}

          ????//我們?cè)O(shè)置一個(gè)簡(jiǎn)單的遞歸方法,沒(méi)有跳出遞歸條件的話,就會(huì)發(fā)生棧內(nèi)存異常
          ????public?static?void?test(){
          ????????test();
          ????}
          }

          我們?cè)O(shè)置一個(gè)簡(jiǎn)單的遞歸方法,但是不給出跳出遞歸條件,這樣的話就會(huì)異。

          運(yùn)行結(jié)果如下:

          運(yùn)行結(jié)果

          這種是線程請(qǐng)求的棧深度超過(guò)虛擬機(jī)所允許的最大深度,拋出StackOverflowError異常,原因就是使用不合理的遞歸造成的。

          我們?cè)賮?lái)看看第二種異常情況:

          /**
          ?*?@author?公眾號(hào):程序員的時(shí)光
          ?*?@create?2020-11-23?10:05
          ?*?@description
          ?*/

          public?class?StackOOM1?{

          ????//線程任務(wù),每個(gè)線程任務(wù)一直在執(zhí)行
          ????private?void?WinStop(){
          ????????while(true){
          ????????????System.out.println(System.currentTimeMillis());
          ????????}
          ????}

          ????//不斷創(chuàng)建線程
          ????public?void?StackByThread(){
          ????????while(true){
          ????????????Thread?thread=new?Thread(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????WinStop();
          ????????????????}
          ????????????});
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????StackOOM1?stackOOM1=new?StackOOM1();
          ????????stackOOM1.StackByThread();
          ????}
          }

          上述代碼的理論上運(yùn)行結(jié)果是:Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread,但是運(yùn)行這段代碼可能會(huì)導(dǎo)致操作系統(tǒng)卡頓,運(yùn)行須謹(jǐn)慎。

          這種是虛擬機(jī)在擴(kuò)展棧時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存空間,拋出OutOfMemoryError異常,原因是不斷創(chuàng)建活躍的線程造成的。


          微信搜索公眾號(hào)《程序員的時(shí)光
          好了,今天就先分享到這里了,下期繼續(xù)給大家?guī)?lái)JVM垃圾回收面試內(nèi)容!
          多干貨、優(yōu)質(zhì)文章,歡迎關(guān)注我的原創(chuàng)技術(shù)公眾號(hào)~

          參考文獻(xiàn):

          [1].深入理解Java虛擬機(jī)(第2版) .作者 周志明


          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  a视频免费看y | 欧美一级在线免费 | 欧色一级黄色视频 | 欧美成人超碰在线 | 亚洲黄网站在线观看 |