<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    JVM經(jīng)典面試20問(wèn)

    共 14932字,需瀏覽 30分鐘

     ·

    2022-01-04 01:44

    今天給大家分享20道JVM??嫉拿嬖囶},答案也整理好了,不會(huì)的快快查漏補(bǔ)缺~

    以下是本期JVM面試題的目錄

    • 講一下JVM的內(nèi)存結(jié)構(gòu)?
    • 說(shuō)一下堆棧的區(qū)別?
    • 什么情況下會(huì)發(fā)生棧溢出?
    • 類文件結(jié)構(gòu)
    • 什么是類加載?類加載的過(guò)程?
    • 什么是雙親委派模型?
    • 為什么需要雙親委派模型?
    • 什么是類加載器,類加載器有哪些?
    • 如何判斷一個(gè)對(duì)象是否存活?
    • 可作為GC Roots的對(duì)象有哪些?
    • 什么情況下類會(huì)被卸載?
    • 強(qiáng)引用、軟引用、弱引用、虛引用是什么?
    • Minor GC 和 Full GC的區(qū)別?
    • 內(nèi)存的分配策略?
    • 垃圾回收算法有哪些?
    • 有哪些垃圾回收器?
    • 常用的 JVM 調(diào)優(yōu)的參數(shù)都有哪些?
    • JVM調(diào)優(yōu)工具有哪些?
    • main方法的執(zhí)行過(guò)程?
    • 對(duì)象的創(chuàng)建過(guò)程?

    講一下JVM的內(nèi)存結(jié)構(gòu)?

    JVM內(nèi)存結(jié)構(gòu)分為5大區(qū)域,程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧、、方法區(qū)。

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

    線程私有的,作為當(dāng)前線程的行號(hào)指示器,用于記錄當(dāng)前虛擬機(jī)正在執(zhí)行的線程指令地址。程序計(jì)數(shù)器主要有兩個(gè)作用:

    1. 當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,通過(guò)它實(shí)現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
    2. 在多線程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,當(dāng)線程被切換回來(lái)的時(shí)候能夠知道它上次執(zhí)行的位置。

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

    虛擬機(jī)棧

    Java 虛擬機(jī)棧是由一個(gè)個(gè)棧幀組成,而每個(gè)棧幀中都擁有:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息。每一次函數(shù)調(diào)用都會(huì)有一個(gè)對(duì)應(yīng)的棧幀被壓入虛擬機(jī)棧,每一個(gè)函數(shù)調(diào)用結(jié)束后,都會(huì)有一個(gè)棧幀被彈出。

    局部變量表是用于存放方法參數(shù)和方法內(nèi)的局部變量。

    每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧所屬方法的符號(hào)引用,在方法調(diào)用過(guò)程中,會(huì)進(jìn)行動(dòng)態(tài)鏈接,將這個(gè)符號(hào)引用轉(zhuǎn)化為直接引用。

    • 部分符號(hào)引用在類加載階段的時(shí)候就轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化就是靜態(tài)鏈接
    • 部分符號(hào)引用在運(yùn)行期間轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化就是動(dòng)態(tài)鏈接

    Java 虛擬機(jī)棧也是線程私有的,每個(gè)線程都有各自的 Java 虛擬機(jī)棧,而且隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的死亡而死亡。Java 虛擬機(jī)棧會(huì)出現(xiàn)兩種錯(cuò)誤:StackOverFlowErrorOutOfMemoryError。

    可以通過(guò)-Xss參數(shù)來(lái)指定每個(gè)線程的虛擬機(jī)棧內(nèi)存大?。?/p>

    java?-Xss2M

    本地方法棧

    虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。Native 方法一般是用其它語(yǔ)言(C、C++等)編寫的。

    本地方法被執(zhí)行的時(shí)候,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、出口信息。

    堆用于存放對(duì)象實(shí)例,是垃圾收集器管理的主要區(qū)域,因此也被稱作GC堆。堆可以細(xì)分為:新生代(Eden空間、From SurvivorTo Survivor空間)和老年代。

    通過(guò) -Xms設(shè)定程序啟動(dòng)時(shí)占用內(nèi)存大小,通過(guò)-Xmx設(shè)定程序運(yùn)行期間最大可占用的內(nèi)存大小。如果程序運(yùn)行需要占用更多的內(nèi)存,超出了這個(gè)設(shè)置值,就會(huì)拋出OutOfMemory異常。

    java?-Xms1M?-Xmx2M

    方法區(qū)

    方法區(qū)與 Java 堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

    對(duì)方法區(qū)進(jìn)行垃圾回收的主要目標(biāo)是對(duì)常量池的回收和對(duì)類的卸載。

    永久代

    方法區(qū)是 JVM 的規(guī)范,而永久代PermGen是方法區(qū)的一種實(shí)現(xiàn)方式,并且只有 HotSpot 有永久代。對(duì)于其他類型的虛擬機(jī),如JRockit沒(méi)有永久代。由于方法區(qū)主要存儲(chǔ)類的相關(guān)信息,所以對(duì)于動(dòng)態(tài)生成類的場(chǎng)景比較容易出現(xiàn)永久代的內(nèi)存溢出。

    元空間

    JDK 1.8 的時(shí)候,HotSpot的永久代被徹底移除了,使用元空間替代。元空間的本質(zhì)和永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。兩者最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用直接內(nèi)存。

    說(shuō)一下堆棧的區(qū)別?

    1. 堆的物理地址分配是不連續(xù)的,性能較慢;棧的物理地址分配是連續(xù)的,性能相對(duì)較快。

    2. 堆存放的是對(duì)象的實(shí)例和數(shù)組;棧存放的是局部變量,操作數(shù)棧,返回結(jié)果等。

    3. 堆是線程共享的;棧是線程私有的。

    什么情況下會(huì)發(fā)生棧溢出?

    • 當(dāng)線程請(qǐng)求的棧深度超過(guò)了虛擬機(jī)允許的最大深度時(shí),會(huì)拋出StackOverFlowError異常。這種情況通常是因?yàn)榉椒ㄟf歸沒(méi)終止條件。
    • 新建線程的時(shí)候沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,虛擬機(jī)會(huì)拋出OutOfMemoryError異常。比如線程啟動(dòng)過(guò)多就會(huì)出現(xiàn)這種情況。

    類文件結(jié)構(gòu)

    Class 文件結(jié)構(gòu)如下:

    ClassFile?{
    ????u4?????????????magic;?//類文件的標(biāo)志
    ????u2?????????????minor_version;//小版本號(hào)
    ????u2?????????????major_version;//大版本號(hào)
    ????u2?????????????constant_pool_count;//常量池的數(shù)量
    ????cp_info????????constant_pool[constant_pool_count-1];//常量池
    ????u2?????????????access_flags;//類的訪問(wèn)標(biāo)記
    ????u2?????????????this_class;//當(dāng)前類的索引
    ????u2?????????????super_class;//父類
    ????u2?????????????interfaces_count;//接口
    ????u2?????????????interfaces[interfaces_count];//一個(gè)類可以實(shí)現(xiàn)多個(gè)接口
    ????u2?????????????fields_count;//字段屬性
    ????field_info?????fields[fields_count];//一個(gè)類會(huì)可以有個(gè)字段
    ????u2?????????????methods_count;//方法數(shù)量
    ????method_info????methods[methods_count];//一個(gè)類可以有個(gè)多個(gè)方法
    ????u2?????????????attributes_count;//此類的屬性表中的屬性數(shù)
    ????attribute_info?attributes[attributes_count];//屬性表集合
    }

    主要參數(shù)如下:

    魔數(shù)class文件標(biāo)志。

    文件版本:高版本的 Java 虛擬機(jī)可以執(zhí)行低版本編譯器生成的類文件,但是低版本的 Java 虛擬機(jī)不能執(zhí)行高版本編譯器生成的類文件。

    常量池:存放字面量和符號(hào)引用。字面量類似于 Java 的常量,如字符串,聲明為final的常量值等。符號(hào)引用包含三類:類和接口的全限定名,方法的名稱和描述符,字段的名稱和描述符。

    訪問(wèn)標(biāo)志:識(shí)別類或者接口的訪問(wèn)信息,比如這個(gè)Class是類還是接口,是否為 public 或者 abstract 類型等等。

    當(dāng)前類的索引:類索引用于確定這個(gè)類的全限定名。

    什么是類加載?類加載的過(guò)程?

    類的加載指的是將類的class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)此類的對(duì)象,通過(guò)這個(gè)對(duì)象可以訪問(wèn)到方法區(qū)對(duì)應(yīng)的類信息。

    加載

    1. 通過(guò)類的全限定名獲取定義此類的二進(jìn)制字節(jié)流
    2. 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
    3. 在內(nèi)存中生成一個(gè)代表該類的Class對(duì)象,作為方法區(qū)類信息的訪問(wèn)入口

    驗(yàn)證

    確保Class文件的字節(jié)流中包含的信息符合虛擬機(jī)規(guī)范,保證在運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。主要包括四種驗(yàn)證:文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證

    準(zhǔn)備

    為類變量分配內(nèi)存并設(shè)置類變量初始值的階段。

    解析

    虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。符號(hào)引用用于描述目標(biāo),直接引用直接指向目標(biāo)的地址。

    初始化

    開(kāi)始執(zhí)行類中定義的Java代碼,初始化階段是調(diào)用類構(gòu)造器的過(guò)程。

    什么是雙親委派模型?

    一個(gè)類加載器收到一個(gè)類的加載請(qǐng)求時(shí),它首先不會(huì)自己嘗試去加載它,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,這樣層層委派,因此所有的加載請(qǐng)求最終都會(huì)傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。

    雙親委派模型的具體實(shí)現(xiàn)代碼在 java.lang.ClassLoader中,此類的 loadClass() 方法運(yùn)行過(guò)程如下:先檢查類是否已經(jīng)加載過(guò),如果沒(méi)有則讓父類加載器去加載。當(dāng)父類加載器加載失敗時(shí)拋出 ClassNotFoundException,此時(shí)嘗試自己去加載。源碼如下:

    public?abstract?class?ClassLoader?{
    ????//?The?parent?class?loader?for?delegation
    ????private?final?ClassLoader?parent;

    ????public?Class?loadClass(String?name)?throws?ClassNotFoundException?{
    ????????return?loadClass(name,?false);
    ????}

    ????protected?Class?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{
    ????????synchronized?(getClassLoadingLock(name))?{
    ????????????//?First,?check?if?the?class?has?already?been?loaded
    ????????????Class?c?=?findLoadedClass(name);
    ????????????if?(c?==?null)?{
    ????????????????try?{
    ????????????????????if?(parent?!=?null)?{
    ????????????????????????//把類加載請(qǐng)求委派給父類加載器去完成
    ????????????????????????c?=?parent.loadClass(name,?false);
    ????????????????????}?else?{
    ????????????????????????c?=?findBootstrapClassOrNull(name);
    ????????????????????}
    ????????????????}?catch?(ClassNotFoundException?e)?{
    ????????????????????//?ClassNotFoundException?thrown?if?class?not?found
    ????????????????????//?from?the?non-null?parent?class?loader
    ????????????????}

    ????????????????if?(c?==?null)?{
    ????????????????????//?If?still?not?found,?then?invoke?findClass?in?order
    ????????????????????//?to?find?the?class.
    ????????????????????c?=?findClass(name);
    ????????????????}
    ????????????}
    ????????????if?(resolve)?{
    ????????????????resolveClass(c);
    ????????????}
    ????????????return?c;
    ????????}
    ????}

    ????protected?Class?findClass(String?name)?throws?ClassNotFoundException?{
    ????????throw?new?ClassNotFoundException(name);
    ????}
    }

    為什么需要雙親委派模型?

    雙親委派模型的好處:可以防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼。如果沒(méi)有雙親委派模型而是由各個(gè)類加載器自行加載的話,如果用戶編寫了一個(gè)java.lang.Object的同名類并放在ClassPath中,多個(gè)類加載器都去加載這個(gè)類到內(nèi)存中,系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,那么類之間的比較結(jié)果及類的唯一性將無(wú)法保證。

    什么是類加載器,類加載器有哪些?

    實(shí)現(xiàn)通過(guò)類的全限定名獲取該類的二進(jìn)制字節(jié)流的代碼塊叫做類加載器。

    主要有以下四種類加載器:

    • 啟動(dòng)類加載器:用來(lái)加載 Java 核心類庫(kù),無(wú)法被 Java 程序直接引用。
    • 擴(kuò)展類加載器:它用來(lái)加載 Java 的擴(kuò)展庫(kù)。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄。該類加載器在此目錄里面查找并加載 Java 類。
    • 系統(tǒng)類加載器:它根據(jù)應(yīng)用的類路徑來(lái)加載 Java 類??赏ㄟ^(guò)ClassLoader.getSystemClassLoader()獲取它。
    • 自定義類加載器:通過(guò)繼承java.lang.ClassLoader類的方式實(shí)現(xiàn)。

    如何判斷一個(gè)對(duì)象是否存活?

    對(duì)堆垃圾回收前的第一步就是要判斷那些對(duì)象已經(jīng)死亡(即不再被任何途徑引用的對(duì)象)。判斷對(duì)象是否存活有兩種方法:引用計(jì)數(shù)法和可達(dá)性分析。

    引用計(jì)數(shù)法

    給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加 1;當(dāng)引用失效,計(jì)數(shù)器就減 1;任何時(shí)候計(jì)數(shù)器為 0 的對(duì)象就是不可能再被使用的。

    這種方法很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。比如下面的代碼,obj1obj2 互相引用,這種情況下,引用計(jì)數(shù)器的值都是1,不會(huì)被垃圾回收。

    public?class?ReferenceCount?{
    ????Object?instance?=?null;
    ?public?static?void?main(String[]?args)?{
    ??ReferenceCount?obj1?=?new?ReferenceCount();
    ??ReferenceCount?obj2?=?new?ReferenceCount();
    ??obj1.instance?=?obj2;
    ??obj2.instance?=?obj1;
    ??obj1?=?null;
    ??obj2?=?null;
    ?}
    }

    可達(dá)性分析

    通過(guò)GC Root對(duì)象為起點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索所走過(guò)的路徑叫引用鏈,當(dāng)一個(gè)對(duì)象到GC Root沒(méi)有任何的引用鏈相連時(shí),說(shuō)明這個(gè)對(duì)象是不可用的。

    可作為GC Roots的對(duì)象有哪些?

    1. 虛擬機(jī)棧中引用的對(duì)象
    2. 本地方法棧中Native方法引用的對(duì)象
    3. 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
    4. 方法區(qū)中常量引用的對(duì)象

    什么情況下類會(huì)被卸載?

    需要同時(shí)滿足以下 3 個(gè)條件,類才可能會(huì)被卸載 :

    • 該類所有的實(shí)例都已經(jīng)被回收。
    • 加載該類的類加載器已經(jīng)被回收。
    • 該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。

    虛擬機(jī)可以對(duì)滿足上述 3 個(gè)條件的類進(jìn)行回收,但不一定會(huì)進(jìn)行回收。

    強(qiáng)引用、軟引用、弱引用、虛引用是什么?

    強(qiáng)引用:在程序中普遍存在的引用賦值,類似Object obj = new Object()這種引用關(guān)系。只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。

    軟引用:如果內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它,如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。

    //軟引用
    SoftReference?softRef?=?new?SoftReference(str);

    弱引用:在進(jìn)行垃圾回收時(shí),不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收只具有弱引用的對(duì)象。

    //弱引用
    WeakReference?weakRef?=?new?WeakReference(str);

    虛引用:虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收。虛引用主要是為了能在對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。

    Minor GC 和 Full GC的區(qū)別?

    • Minor GC:回收新生代,因?yàn)樾律鷮?duì)象存活時(shí)間很短,因此 Minor GC會(huì)頻繁執(zhí)行,執(zhí)行的速度一般也會(huì)比較快。

    • Full GC:回收老年代和新生代,老年代的對(duì)象存活時(shí)間長(zhǎng),因此 Full GC 很少執(zhí)行,執(zhí)行速度會(huì)比 Minor GC 慢很多。

    內(nèi)存的分配策略?

    對(duì)象優(yōu)先在 Eden 分配

    大多數(shù)情況下,對(duì)象在新生代 Eden 上分配,當(dāng) Eden 空間不夠時(shí),觸發(fā) Minor GC。

    大對(duì)象直接進(jìn)入老年代

    大對(duì)象是指需要連續(xù)內(nèi)存空間的對(duì)象,最典型的大對(duì)象有長(zhǎng)字符串和大數(shù)組??梢栽O(shè)置JVM參數(shù) -XX:PretenureSizeThreshold,大于此值的對(duì)象直接在老年代分配。

    長(zhǎng)期存活的對(duì)象進(jìn)入老年代

    通過(guò)參數(shù) -XX:MaxTenuringThreshold 可以設(shè)置對(duì)象進(jìn)入老年代的年齡閾值。對(duì)象在Survivor區(qū)每經(jīng)過(guò)一次 Minor GC,年齡就增加 1 歲,當(dāng)它的年齡增加到一定程度,就會(huì)被晉升到老年代中。

    動(dòng)態(tài)對(duì)象年齡判定

    并非對(duì)象的年齡必須達(dá)到 MaxTenuringThreshold 才能晉升老年代,如果在 Survivor 中相同年齡所有對(duì)象大小的總和大于 Survivor 空間的一半,則年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代,無(wú)需達(dá)到 MaxTenuringThreshold 年齡閾值。

    空間分配擔(dān)保

    在發(fā)生 Minor GC 之前,虛擬機(jī)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果條件成立的話,那么 Minor GC 是安全的。如果不成立的話虛擬機(jī)會(huì)查看 HandlePromotionFailure 的值是否允許擔(dān)保失敗。如果允許,那么就會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試著進(jìn)行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值為不允許擔(dān)保失敗,那么就要進(jìn)行一次 Full GC。

    垃圾回收算法有哪些?

    垃圾回收算法有四種,分別是標(biāo)記清除法、標(biāo)記整理法、復(fù)制算法、分代收集算法。

    標(biāo)記清除算法

    首先利用可達(dá)性去遍歷內(nèi)存,把存活對(duì)象和垃圾對(duì)象進(jìn)行標(biāo)記。標(biāo)記結(jié)束后統(tǒng)一將所有標(biāo)記的對(duì)象回收掉。這種垃圾回收算法效率較低,并且會(huì)產(chǎn)生大量不連續(xù)的空間碎片。

    復(fù)制清除算法

    半?yún)^(qū)復(fù)制,用于新生代垃圾回收。將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對(duì)象復(fù)制到另一塊去,然后再把使用的空間一次清理掉。

    特點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效,但可用內(nèi)存縮小為了原來(lái)的一半,浪費(fèi)空間。

    標(biāo)記整理算法

    根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法,標(biāo)記過(guò)程仍然與標(biāo)記-清除算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。

    分類收集算法

    根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>

    一般將堆分為新生代和老年代。

    • 新生代使用復(fù)制算法
    • 老年代使用標(biāo)記清除算法或者標(biāo)記整理算法

    在新生代中,每次垃圾收集時(shí)都有大批對(duì)象死去,只有少量存活,使用復(fù)制算法比較合適,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。老年代對(duì)象存活率高,適合使用標(biāo)記-清理或者標(biāo)記-整理算法進(jìn)行垃圾回收。

    有哪些垃圾回收器?

    垃圾回收器主要分為以下幾種:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1。

    Serial 收集器

    單線程收集器,使用一個(gè)垃圾收集線程去進(jìn)行垃圾回收,在進(jìn)行垃圾回收的時(shí)候必須暫停其他所有的工作線程( Stop The World ),直到它收集結(jié)束。

    特點(diǎn):簡(jiǎn)單高效;內(nèi)存消耗?。粵](méi)有線程交互的開(kāi)銷,單線程收集效率高;需暫停所有的工作線程,用戶體驗(yàn)不好。

    ParNew 收集器

    Serial收集器的多線程版本,除了使用多線程進(jìn)行垃圾收集外,其他行為、參數(shù)與 Serial 收集器基本一致。

    Parallel Scavenge 收集器

    新生代收集器,基于復(fù)制清除算法實(shí)現(xiàn)的收集器。特點(diǎn)是吞吐量?jī)?yōu)先,能夠并行收集的多線程收集器,允許多個(gè)垃圾回收線程同時(shí)運(yùn)行,降低垃圾收集時(shí)間,提高吞吐量。所謂吞吐量就是 CPU 中用于運(yùn)行用戶代碼的時(shí)間與 CPU 總消耗時(shí)間的比值(吞吐量 = 運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間))。Parallel Scavenge 收集器關(guān)注點(diǎn)是吞吐量,高效率的利用 CPU 資源。CMS 垃圾收集器關(guān)注點(diǎn)更多的是用戶線程的停頓時(shí)間。

    Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù)以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。

    • -XX:MaxGCPauseMillis參數(shù)的值是一個(gè)大于0的毫秒數(shù),收集器將盡量保證內(nèi)存回收花費(fèi)的時(shí)間不超過(guò)用戶設(shè)定值。

    • -XX:GCTimeRatio參數(shù)的值大于0小于100,即垃圾收集時(shí)間占總時(shí)間的比率,相當(dāng)于吞吐量的倒數(shù)。

    Serial Old 收集器

    Serial 收集器的老年代版本,單線程收集器,使用標(biāo)記整理算法

    Parallel Old 收集器

    Parallel Scavenge 收集器的老年代版本。多線程垃圾收集,使用標(biāo)記整理算法。

    CMS 收集器

    Concurrent Mark Sweep ,并發(fā)標(biāo)記清除,追求獲取最短停頓時(shí)間,實(shí)現(xiàn)了讓垃圾收集線程與用戶線程基本上同時(shí)工作

    CMS 垃圾回收基于標(biāo)記清除算法實(shí)現(xiàn),整個(gè)過(guò)程分為四個(gè)步驟:

    • 初始標(biāo)記:暫停所有用戶線程(Stop The World),記錄直接與 GC Roots 直接相連的對(duì)象 。
    • 并發(fā)標(biāo)記:從GC Roots開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活對(duì)象,耗時(shí)較長(zhǎng),但是不需要停頓用戶線程。
    • 重新標(biāo)記:在并發(fā)標(biāo)記期間對(duì)象的引用關(guān)系可能會(huì)變化,需要重新進(jìn)行標(biāo)記。此階段也會(huì)暫停所有用戶線程。
    • 并發(fā)清除:清除標(biāo)記對(duì)象,這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。

    在整個(gè)過(guò)程中,耗時(shí)最長(zhǎng)的是并發(fā)標(biāo)記和并發(fā)清除階段,這兩個(gè)階段垃圾收集線程都可以與用戶線程一起工作,所以從總體上來(lái)說(shuō),CMS收集器的內(nèi)存回收過(guò)程是與用戶線程一起并發(fā)執(zhí)行的。

    優(yōu)點(diǎn):并發(fā)收集,停頓時(shí)間短。

    缺點(diǎn)

    • 標(biāo)記清除算法導(dǎo)致收集結(jié)束有大量空間碎片
    • 產(chǎn)生浮動(dòng)垃圾,在并發(fā)清理階段用戶線程還在運(yùn)行,會(huì)不斷有新的垃圾產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過(guò)程之后,CMS無(wú)法在當(dāng)次收集中回收它們,只好等到下一次垃圾回收再處理;

    G1收集器

    G1垃圾收集器的目標(biāo)是在不同應(yīng)用場(chǎng)景中追求高吞吐量和低停頓之間的最佳平衡

    G1將整個(gè)堆分成相同大小的分區(qū)(Region),有四種不同類型的分區(qū):Eden、Survivor、Old和Humongous。分區(qū)的大小取值范圍為 1M 到 32M,都是2的冪次方。分區(qū)大小可以通過(guò)-XX:G1HeapRegionSize參數(shù)指定。Humongous區(qū)域用于存儲(chǔ)大對(duì)象。G1規(guī)定只要大小超過(guò)了一個(gè)分區(qū)容量一半的對(duì)象就認(rèn)為是大對(duì)象。

    G1 收集器對(duì)各個(gè)分區(qū)回收所獲得的空間大小和回收所需時(shí)間的經(jīng)驗(yàn)值進(jìn)行排序,得到一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)置的最大回收停頓時(shí)間,優(yōu)先回收價(jià)值最大的分區(qū)。

    特點(diǎn):可以由用戶指定期望的垃圾收集停頓時(shí)間。

    G1 收集器的回收過(guò)程分為以下幾個(gè)步驟:

    • 初始標(biāo)記。暫停所有其他線程,記錄直接與 GC Roots 直接相連的對(duì)象,耗時(shí)較短 。
    • 并發(fā)標(biāo)記。從GC Roots開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出要回收的對(duì)象,耗時(shí)較長(zhǎng),不過(guò)可以和用戶程序并發(fā)執(zhí)行。
    • 最終標(biāo)記。需對(duì)其他線程做短暫的暫停,用于處理并發(fā)標(biāo)記階段對(duì)象引用出現(xiàn)變動(dòng)的區(qū)域。
    • 篩選回收。對(duì)各個(gè)分區(qū)的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來(lái)制定回收計(jì)劃,然后把決定回收的分區(qū)的存活對(duì)象復(fù)制到空的分區(qū)中,再清理掉整個(gè)舊的分區(qū)的全部空間。這里的操作涉及存活對(duì)象的移動(dòng),會(huì)暫停用戶線程,由多條收集器線程并行完成。

    常用的 JVM 調(diào)優(yōu)的參數(shù)都有哪些?

    • -Xms1g:初始化堆大小為 1g;
    • -Xmx1g:堆最大內(nèi)存為 1g;
    • -XX:NewRatio=4:設(shè)置新生代和老年代的內(nèi)存比例為 1:4;
    • -XX:SurvivorRatio=8:設(shè)置 EdenSurvivor 比例為 8:2;
    • -XX:+PrintGC:打印 GC 信息;
    • -XX:+PrintGCDetails:打印 GC 詳細(xì)信息;
    • –XX:+UseParNewGC:指定使用 ParNew+ Serial Old 垃圾回收器;
    • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器;
    • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器。

    JVM調(diào)優(yōu)工具有哪些?

    jps:列出本機(jī)所有 Java 進(jìn)程的進(jìn)程號(hào)

    常用參數(shù)如下:

    • -m 輸出main方法的參數(shù)
    • -l 輸出完全的包名和應(yīng)用主類名
    • -v 輸出JVM參數(shù)
    jps?-lvm
    //output
    //4124?com.zzx.Application?-javaagent:E:\IDEA2019\lib\idea_rt.jar=10291:E:\IDEA2019\bin?-Dfile.encoding=UTF-8

    jstack:查看某個(gè) Java 進(jìn)程內(nèi)的線程堆棧信息。使用參數(shù)-l可以打印額外的鎖信息,發(fā)生死鎖時(shí)可以使用jstack -l pid觀察鎖持有情況。

    jstack?-l?4124?|?more

    輸出結(jié)果如下:

    "http-nio-8001-exec-10"?#40?daemon?prio=5?os_prio=0?tid=0x000000002542f000?nid=0x4028?waiting?on?condition?[0x000000002cc9e000]
    ???java.lang.Thread.State:?WAITING?(parking)
    ????????at?sun.misc.Unsafe.park(Native?Method)
    ????????-?parking?to?wait?for??<0x000000077420d7e8>?(a?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    ????????at?java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    ????????at?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    ????????at?java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    ????????at?org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
    ????????at?org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
    ????????at?java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    ????????at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    ????????at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    ????????at?org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    ????????at?java.lang.Thread.run(Thread.java:748)

    ???Locked?ownable?synchronizers:
    ????????-?None

    WAITING (parking)指線程處于掛起中,在等待某個(gè)條件發(fā)生,來(lái)把自己?jiǎn)拘选?/p>

    jstat:用于查看虛擬機(jī)各種運(yùn)行狀態(tài)信息(類裝載、內(nèi)存、垃圾收集等運(yùn)行數(shù)據(jù))。使用參數(shù)-gcuitl可以查看垃圾回收的統(tǒng)計(jì)信息。

    jstat?-gcutil?4124
    ??S0?????S1?????E??????O??????M?????CCS????YGC?????YGCT????FGC????FGCT?????GCT
    ??0.00???0.00??67.21??19.20??96.36??94.96?????10????0.084?????3????0.191????0.275

    參數(shù)說(shuō)明:

    • S0Survivor0區(qū)當(dāng)前使用比例
    • S1Survivor1區(qū)當(dāng)前使用比例
    • EEden區(qū)使用比例
    • O:老年代使用比例
    • M:元數(shù)據(jù)區(qū)使用比例
    • CCS:壓縮使用比例
    • YGC:年輕代垃圾回收次數(shù)
    • FGC:老年代垃圾回收次數(shù)
    • FGCT:老年代垃圾回收消耗時(shí)間
    • GCT:垃圾回收消耗總時(shí)間

    jmap:查看堆內(nèi)存快照。通過(guò)jmap命令可以獲得運(yùn)行中的堆內(nèi)存的快照,從而可以對(duì)堆內(nèi)存進(jìn)行離線分析。

    查詢進(jìn)程4124的堆內(nèi)存快照,輸出結(jié)果如下:

    >jmap?-heap?4124
    Attaching?to?process?ID?4124,?please?wait...
    Debugger?attached?successfully.
    Server?compiler?detected.
    JVM?version?is?25.221-b11

    using?thread-local?object?allocation.
    Parallel?GC?with?6?thread(s)

    Heap?Configuration:
    ???MinHeapFreeRatio?????????=?0
    ???MaxHeapFreeRatio?????????=?100
    ???MaxHeapSize??????????????=?4238344192?(4042.0MB)
    ???NewSize??????????????????=?88604672?(84.5MB)
    ???MaxNewSize???????????????=?1412431872?(1347.0MB)
    ???OldSize??????????????????=?177733632?(169.5MB)
    ???NewRatio?????????????????=?2
    ???SurvivorRatio????????????=?8
    ???MetaspaceSize????????????=?21807104?(20.796875MB)
    ???CompressedClassSpaceSize?=?1073741824?(1024.0MB)
    ???MaxMetaspaceSize?????????=?17592186044415?MB
    ???G1HeapRegionSize?????????=?0?(0.0MB)

    Heap?Usage:
    PS?Young?Generation
    Eden?Space:
    ???capacity?=?327155712?(312.0MB)
    ???used?????=?223702392?(213.33922576904297MB)
    ???free?????=?103453320?(98.66077423095703MB)
    ???68.37795697725736%?used
    From?Space:
    ???capacity?=?21495808?(20.5MB)
    ???used?????=?0?(0.0MB)
    ???free?????=?21495808?(20.5MB)
    ???0.0%?used
    To?Space:
    ???capacity?=?23068672?(22.0MB)
    ???used?????=?0?(0.0MB)
    ???free?????=?23068672?(22.0MB)
    ???0.0%?used
    PS?Old?Generation
    ???capacity?=?217579520?(207.5MB)
    ???used?????=?41781472?(39.845916748046875MB)
    ???free?????=?175798048?(167.65408325195312MB)
    ???19.20285144484187%?used

    27776?interned?Strings?occupying?3262336?bytes.

    main方法的執(zhí)行過(guò)程?

    以下是示例代碼:

    public?class?Application?{
    ????public?static?void?main(String[]?args)?{
    ????????Person?p?=?new?Person("大彬");
    ????????p.getName();
    ????}
    }

    class?Person?{
    ????public?String?name;

    ????public?Person(String?name)?{
    ????????this.name?=?name;
    ????}

    ????public?String?getName()?{
    ????????return?this.name;
    ????}
    }

    執(zhí)行main方法的過(guò)程如下:

    1. 編譯Application.java后得到 Application.class 后,執(zhí)行這個(gè)class文件,系統(tǒng)會(huì)啟動(dòng)一個(gè) JVM 進(jìn)程,從類路徑中找到一個(gè)名為 Application.class 的二進(jìn)制文件,將 Application 類信息加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),這個(gè)過(guò)程叫做類的加載。
    2. JVM 找到 Application 的主程序入口,執(zhí)行main方法。
    3. main方法的第一條語(yǔ)句為 Person p = new Person("大彬"),就是讓 JVM 創(chuàng)建一個(gè)Person對(duì)象,但是這個(gè)時(shí)候方法區(qū)中是沒(méi)有 Person 類的信息的,所以 JVM 馬上加載 Person 類,把 Person 類的信息放到方法區(qū)中。
    4. 加載完 Person 類后,JVM 在堆中分配內(nèi)存給 Person 對(duì)象,然后調(diào)用構(gòu)造函數(shù)初始化 Person 對(duì)象,這個(gè) Person 對(duì)象持有指向方法區(qū)中的 Person 類的類型信息的引用。
    5. 執(zhí)行p.getName()時(shí),JVM 根據(jù) p 的引用找到 p 所指向的對(duì)象,然后根據(jù)此對(duì)象持有的引用定位到方法區(qū)中 Person 類的類型信息的方法表,獲得 getName() 的字節(jié)碼地址。
    6. 執(zhí)行getName()方法。

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

    1. 類加載檢查:當(dāng)虛擬機(jī)遇到一條 new 指令時(shí),首先檢查是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過(guò)、解析和初始化過(guò)。如果沒(méi)有,那先執(zhí)行類加載。
    2. 分配內(nèi)存:在類加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為對(duì)象實(shí)例分配內(nèi)存。
    3. 初始化。分配到的內(nèi)存空間都初始化為零值,通過(guò)這個(gè)操作保證了對(duì)象的字段可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
    4. 設(shè)置對(duì)象頭。Hotspot 虛擬機(jī)的對(duì)象頭包括:存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)(哈希碼、分代年齡、鎖標(biāo)志等等)、類型指針和數(shù)據(jù)長(zhǎng)度(數(shù)組對(duì)象才有),類型指針就是對(duì)象指向它的類信息的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。
    5. 按照Java代碼進(jìn)行初始化。

    參考資料

    • 周志明. 深入理解 Java 虛擬機(jī) [M]. 機(jī)械工業(yè)出版社




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

    手機(jī)掃一掃分享

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

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    蜜桃av免费 | 91cao狠狠 | 殴美操逼视频 | 在线A∨视频 | 18禁网站免费91 |