成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

三分鐘帶你了解對(duì)象的創(chuàng)建過(guò)程

開(kāi)發(fā) 前端
Java 中最典型的聚合量是對(duì)象,如果逃逸分析證明一個(gè)對(duì)象不會(huì)被外部訪問(wèn),并且這個(gè)對(duì)象是可分解的,那程序真正執(zhí)行的時(shí)候?qū)⒖赡懿粍?chuàng)建這個(gè)對(duì)象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來(lái)代替,拆散后的變量便可以被單獨(dú)分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對(duì)象就無(wú)需整體分配空間了。

一、摘要

在之前的文章中,我們介紹了類(lèi)加載的過(guò)程和 JVM 內(nèi)存布局相關(guān)的知識(shí)。本篇我們綜合之前的知識(shí),結(jié)合代碼一起推演一下對(duì)象的真實(shí)創(chuàng)建過(guò)程,以及對(duì)象創(chuàng)建完成之后在 JVM 中是如何保存的。

二、對(duì)象的創(chuàng)建

在 Java 中,創(chuàng)建對(duì)象的方式有很多種,比如最常見(jiàn)的通過(guò)new xxx()來(lái)創(chuàng)建一個(gè)對(duì)象,通過(guò)反射Class.forName(xxx).newInstance()來(lái)創(chuàng)建對(duì)象等。其實(shí)無(wú)論是哪種創(chuàng)建方式,JVM 底層的執(zhí)行過(guò)程是一樣的。

對(duì)象的創(chuàng)建過(guò)程,可以用如下圖來(lái)簡(jiǎn)要概括。

圖片圖片

創(chuàng)建對(duì)象大致分為 5 個(gè)步驟:

1.檢查類(lèi)是否加載,如果沒(méi)有就先執(zhí)行類(lèi)的加載

2.分配內(nèi)存

3.初始化零值

4.設(shè)置頭對(duì)象

5.執(zhí)行初始化方法,例如構(gòu)造方法等

下面我們一起來(lái)看下每個(gè)步驟具體的工作內(nèi)容。

2.1、類(lèi)加載檢查

當(dāng)需要?jiǎng)?chuàng)建一個(gè)類(lèi)的實(shí)例對(duì)象時(shí),比如通過(guò)new xxx()方式,虛擬機(jī)首先會(huì)去檢查這個(gè)類(lèi)是否在常量池中能定位到一個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已經(jīng)被加載、解析和初始化,如果沒(méi)有,那么必須先執(zhí)行類(lèi)的加載流程;如果已經(jīng)加載過(guò)了,會(huì)在堆區(qū)有一個(gè)類(lèi)的 class 對(duì)象,方法區(qū)會(huì)有類(lèi)的相關(guān)元數(shù)據(jù)信息。

為什么在對(duì)象創(chuàng)建時(shí),需要有這一個(gè)檢查判斷?

主要原因在于:類(lèi)的加載,通常都是懶加載,只有當(dāng)使用類(lèi)的時(shí)候才會(huì)加載,所以先要有這個(gè)判斷流程。

關(guān)于類(lèi)的加載過(guò)程,在之前的文章中已經(jīng)有所介紹,有興趣的朋友可以翻看之前的文章。

2.2、分配內(nèi)存

類(lèi)加載成功后,虛擬機(jī)就能夠確定對(duì)象的大小了,此時(shí)虛擬機(jī)會(huì)在堆內(nèi)存中劃分一塊對(duì)象大小的內(nèi)存空間出來(lái),分配給新生對(duì)象。

虛擬機(jī)如何在堆中分配內(nèi)存的呢?

主要有兩種方式:

1.指針碰撞法

2.空閑列表法

下面我們一起來(lái)看看相關(guān)的內(nèi)存分配方式。

2.2.1、指針碰撞法

如果內(nèi)存是規(guī)整的,那么虛擬機(jī)將采用指針碰撞法來(lái)為對(duì)象分配內(nèi)存。

指針碰撞法,簡(jiǎn)單的說(shuō)就是所有用過(guò)的內(nèi)存在一邊,空閑的內(nèi)存在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存時(shí)會(huì)把指針向空閑一方挪動(dòng)一段,直到能容納對(duì)象大小的位置。

如果垃圾收集器選擇的是 Serial、ParNew 這種基于壓縮算法的,虛擬機(jī)會(huì)采用這種分配方式。

2.2.2、空閑列表法

如果內(nèi)存不是規(guī)整的,已使用的內(nèi)存和未使用的內(nèi)存相互交錯(cuò),那么虛擬機(jī)將采用空閑列表法來(lái)為對(duì)象分配內(nèi)存。

空閑列表法,簡(jiǎn)單的說(shuō)就是在虛擬機(jī)內(nèi)部維護(hù)了一個(gè)列表,會(huì)記錄哪些內(nèi)存塊是可用的,在分配的時(shí)候會(huì)從列表中找到一塊能容納對(duì)象大小的空間,劃分給對(duì)象實(shí)例,并更新列表上的內(nèi)容。

如果垃圾收集器選擇的是 CMS 這種基于標(biāo)記-清除算法的,虛擬機(jī)會(huì)采用這種分配方式。

2.2.3、內(nèi)存分配安全問(wèn)題

我們知道,虛擬機(jī)是支持多個(gè)線程同時(shí)分配內(nèi)存的,是否會(huì)有線程安全的問(wèn)題呢?

答案是:肯定存在的。比如用指針碰撞法時(shí),虛擬機(jī)正在給對(duì)象 A 分配內(nèi)存,但指針還沒(méi)來(lái)及修改,此時(shí)又有一個(gè)線程給對(duì)象 B 分配內(nèi)存,同時(shí)使用了原來(lái)的指針來(lái)分配,最后的結(jié)果就是這個(gè)區(qū)域只分配來(lái)一個(gè)對(duì)象,另一個(gè)對(duì)象被覆蓋了。

針對(duì)內(nèi)存分配時(shí)存在的線程安全問(wèn)題,虛擬機(jī)采用了兩種方式來(lái)進(jìn)行處理:

  • CAS+重試機(jī)制:通過(guò) CAS 操作移動(dòng)指針,只有一個(gè)線程可以移動(dòng)成功,移動(dòng)失敗的線程重試,直到成功為止
  • TLAB (thread local Allocation buffer):也稱(chēng)為本地線程分配緩沖,這個(gè)處理方式思想很簡(jiǎn)單,就是當(dāng)線程開(kāi)啟時(shí),虛擬機(jī)會(huì)為每個(gè)線程分配一塊較大的空間,然后線程內(nèi)部創(chuàng)建對(duì)象的時(shí)候,就從自己的空間分配,這樣就不會(huì)有并發(fā)問(wèn)題了,當(dāng)線程自己的空間用完了才會(huì)從堆中分配內(nèi)存,之后會(huì)轉(zhuǎn)為通過(guò) CAS+重試機(jī)制來(lái)解決并發(fā)問(wèn)題

以上就是虛擬機(jī)解決對(duì)象內(nèi)存分配時(shí)存在的線程安全問(wèn)題的措施。

2.3、初始化零值

初始化零值,顧名思義,就是對(duì)分配的這一塊內(nèi)存初始化零值,也就是給實(shí)例對(duì)象的成員變量賦于零值,比如 int 類(lèi)型賦值為 0,引用類(lèi)型為null等操作。這樣對(duì)象就可以在沒(méi)有賦值情況下使用了,只不過(guò)訪問(wèn)對(duì)象的成員變量都是零值。

2.4、設(shè)置頭對(duì)象

初始化零值完成之后,虛擬機(jī)就會(huì)對(duì)對(duì)象進(jìn)行必要的設(shè)置,比如這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例、如何才能找到類(lèi)的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息,這些信息都會(huì)存放在對(duì)象頭中。這部分?jǐn)?shù)據(jù),官方稱(chēng)它為“Mark Word”。

在 HotSpot 虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為 3 塊區(qū)域:對(duì)象頭 (Header)、 實(shí)例數(shù)據(jù) (Instance Data) 和對(duì)齊填充位 (Padding)。

以 32 位的虛擬機(jī)為例,對(duì)象的組成可以用如下圖來(lái)簡(jiǎn)要概括。(64位的虛擬機(jī)略有不同)

圖片圖片

各部分區(qū)域功能如下:

  • 對(duì)象頭:分為 Mark Word 和元數(shù)據(jù)區(qū),如果是數(shù)組對(duì)象,還有記錄數(shù)組長(zhǎng)度的區(qū)域。這三塊保存著對(duì)象的 hashCode 值,鎖的狀態(tài),類(lèi)元數(shù)據(jù)指針,對(duì)象的分代年齡等等信息。
  • 實(shí)例數(shù)據(jù):顧名思義,用于保存對(duì)象成員變量的值,如果變量是引用類(lèi)型,保存的是內(nèi)存地址
  • 對(duì)齊填充位:因?yàn)?HotSpot 虛擬機(jī)要求對(duì)象的起止地址必須是 8 字節(jié)的整數(shù)倍,也就是要求對(duì)象的大小為 8 字節(jié)的整數(shù)倍,如果不足  8 字節(jié)的整數(shù)倍,那么就需要通過(guò)對(duì)齊填充進(jìn)行占位,補(bǔ)夠 8 字節(jié)的整數(shù)倍。

我們重點(diǎn)來(lái)看下 Mark Word 的組成,不同的操作系統(tǒng)環(huán)境下占用的空間不同,在 32 位操作系統(tǒng)中占 4 個(gè)字節(jié),在 64 位中占 8 個(gè)字節(jié)。

以 32 位操作系統(tǒng)為例,Mark Word 內(nèi)部結(jié)構(gòu)如下:

圖片圖片

各部分的含義如下:

  • identity_hashcode:25 位的對(duì)象標(biāo)識(shí)哈希碼。采用延遲加載技術(shù),調(diào)用System.identityHashCode()方法獲取,并會(huì)將結(jié)果寫(xiě)到該對(duì)象頭中。當(dāng)對(duì)象被鎖定時(shí),該值會(huì)移動(dòng)到管程 Monitor 中。
  • age:4 位的 Java 對(duì)象年齡。在GC中,如果對(duì)象在 Survivor 區(qū)復(fù)制一次,年齡增加 1,當(dāng)對(duì)象達(dá)到設(shè)定的閾值時(shí),將會(huì)晉升到老年代。默認(rèn)情況下,并行 GC 的年齡閾值為 15,并發(fā) GC 的年齡閾值為 6。由于 age 只有4位,所以最大值為15,這就是為什么-XX:MaxTenuringThreshold選項(xiàng)最大值為 15 的原因。
  • lock:2 位的鎖狀態(tài)標(biāo)記位。對(duì)象的加鎖狀態(tài)分為無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖等幾種標(biāo)記,不同的標(biāo)記值,表示的含義也不同。
  • biased_lock:對(duì)象是否啟用偏向鎖標(biāo)記,只占 1 個(gè)二進(jìn)制位。為 1 時(shí)表示對(duì)象啟用偏向鎖,為 0 時(shí)表示對(duì)象沒(méi)有偏向鎖。偏向鎖是一種鎖的優(yōu)化手段,開(kāi)啟偏向鎖,某些時(shí)候可以省去線程頻繁申請(qǐng)鎖的操作,提升程序執(zhí)行性能。
  • thread:持有偏向鎖的線程 ID,如果該線程再次訪問(wèn)這個(gè)鎖的代碼塊,可以直接訪問(wèn)
  • epoch:偏向鎖在 CAS 鎖操作過(guò)程中的標(biāo)識(shí)
  • ptr_to_lock_record:在輕量級(jí)鎖時(shí),指向棧中鎖記錄的指針
  • ptr_to_heavyweight_monitor:在重量級(jí)鎖時(shí),指向管程 Monitor 的指針

其中l(wèi)ock參數(shù)中不同的標(biāo)記值,表示的含義如下。

圖片圖片

lock標(biāo)記位,通常會(huì)在使用到synchronized關(guān)鍵字的對(duì)象上發(fā)揮作用。隨著線程之間競(jìng)爭(zhēng)激烈程度,對(duì)象鎖會(huì)從無(wú)鎖狀態(tài)逐漸升級(jí)到重量級(jí)鎖,其中的變化過(guò)程,可以用如下步驟來(lái)概括。

1.初期鎖對(duì)象剛創(chuàng)建時(shí),還沒(méi)有任何線程來(lái)競(jìng)爭(zhēng),鎖狀態(tài)為 01,偏向鎖標(biāo)識(shí)位是0(無(wú)線程競(jìng)爭(zhēng)它),此時(shí)程序執(zhí)行效率最高。

2.當(dāng)有一個(gè)線程來(lái)競(jìng)爭(zhēng)鎖時(shí),先用偏向鎖,表示鎖對(duì)象偏愛(ài)這個(gè)線程,這個(gè)線程要執(zhí)行這個(gè)鎖關(guān)聯(lián)的任何代碼,不需要再做任何檢查和切換,這種競(jìng)爭(zhēng)不激烈的情況下,效率也非常高。

3.當(dāng)有兩個(gè)線程開(kāi)始競(jìng)爭(zhēng)這個(gè)鎖對(duì)象時(shí),情況會(huì)發(fā)生變化,鎖會(huì)升級(jí)為輕量級(jí)鎖,兩個(gè)線程公平競(jìng)爭(zhēng),哪個(gè)線程先占有鎖對(duì)象并執(zhí)行代碼,鎖對(duì)象的 Mark Word 就執(zhí)行哪個(gè)線程的棧幀中的鎖記錄。輕量級(jí)鎖在加鎖過(guò)程中,用到了自旋鎖。所謂自旋,就是指當(dāng)有另外一個(gè)線程來(lái)競(jìng)爭(zhēng)鎖時(shí),這個(gè)線程會(huì)在原地循環(huán)等待,而不是把該線程給阻塞,直到那個(gè)獲得鎖的線程釋放鎖之后,這個(gè)線程就可以馬上獲得鎖,執(zhí)行效率有所衰減。

4.如果競(jìng)爭(zhēng)這個(gè)鎖對(duì)象的線程越來(lái)越多,會(huì)導(dǎo)致更多的切換和等待,JVM 會(huì)把該對(duì)象的鎖升級(jí)為重量級(jí)鎖。這個(gè)就是大家常說(shuō)的同步鎖,此時(shí)對(duì)象中的 Mark Word 會(huì)再次發(fā)生變化,會(huì)指向一個(gè)監(jiān)視器 (Monitor) 對(duì)象,這個(gè)監(jiān)視器對(duì)象用集合的形式來(lái)登記和管理排隊(duì)的線程。Monitor 依賴操作系統(tǒng)的 MutexLock(互斥鎖)來(lái)實(shí)現(xiàn)線程排隊(duì),線程被阻塞后便進(jìn)入內(nèi)核(Linux)調(diào)度狀態(tài),這個(gè)會(huì)導(dǎo)致系統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來(lái)回切換線程,相比其它級(jí)別的鎖,此時(shí)鎖的性能最差。

關(guān)于synchronized關(guān)鍵字原理分析,我們會(huì)在后續(xù)的文章中再次介紹。

2.5、執(zhí)行init方法

執(zhí)行 init 方法是對(duì)象創(chuàng)建的最后一步,虛擬機(jī)會(huì)給對(duì)象的成員變量設(shè)置用戶指定的初始值,并且會(huì)執(zhí)行構(gòu)造方法等。

2.6、小結(jié)

以上就是對(duì)象的創(chuàng)建過(guò)程,最后我們通過(guò)工具來(lái)看下對(duì)象創(chuàng)建后的大小。

可以添加第三方j(luò)ol包,使用它來(lái)打印對(duì)象的內(nèi)存布局情況。

<dependency>
   <groupId>org.openjdk.jol</groupId>
   <artifactId>jol-core</artifactId>
   <version>0.9</version>
</dependency>

編寫(xiě)一個(gè)測(cè)試類(lèi)。

public class ObjectHeaderTest {

    public static void main(String[] args) {
        System.out.println("=========打印Object對(duì)象的大小========");
        ClassLayout layout = ClassLayout.parseInstance(new Object());
        System.out.println(layout.toPrintable());


        System.out.println("========打印數(shù)組對(duì)象的大小=========");
        ClassLayout layout1 = ClassLayout.parseInstance(new int[]{});
        System.out.println(layout1.toPrintable());


        System.out.println("========打印有成員變量的對(duì)象大小=========");
        ClassLayout layout2 = ClassLayout.parseInstance(new ArtisanTest());
        System.out.println(layout2.toPrintable());
    }

    /**
     * ‐XX:+UseCompressedOops 表示開(kāi)啟壓縮普通對(duì)象指針
     * ‐XX:+UseCompressedClassPointers 表示開(kāi)啟壓縮類(lèi)指針
     *
     */
    public static class ArtisanTest {

        int id;        //4B
        String name;   //4B
        byte b;        //1B
        Object o;      //4B
    }
}

輸出結(jié)果:

=========打印Object對(duì)象的大小========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

========打印數(shù)組對(duì)象的大小=========
[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     0    int [I.<elements>                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

========打印有成員變量的對(duì)象大小=========
com.example.thread.o4.ObjectHeaderTest$ArtisanTest object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           12 f2 00 f8 (00010010 11110010 00000000 11111000) (-134155758)
     12     4                int ArtisanTest.id                            0
     16     1               byte ArtisanTest.b                             0
     17     3                    (alignment/padding gap)                  
     20     4   java.lang.String ArtisanTest.name                          null
     24     4   java.lang.Object ArtisanTest.o                             null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

三、對(duì)象的訪問(wèn)方式

對(duì)象創(chuàng)建完之后,剩下的工作就是使用對(duì)象。Java 程序主要通過(guò)虛擬機(jī)棧上的 reference (引用) 數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。

對(duì)象的訪問(wèn)方式有虛擬機(jī)實(shí)現(xiàn)而定,不同的虛擬機(jī)實(shí)現(xiàn)訪問(wèn)方式不同,目前主流的訪問(wèn)方式有:

  • 句柄訪問(wèn)
  • 直接指針訪問(wèn)

3.1、句柄訪問(wèn)

句柄訪問(wèn),虛擬機(jī)會(huì)在 Java 堆中劃分出一塊內(nèi)存來(lái)作為句柄池,reference 中存儲(chǔ)的就是對(duì)象的句柄地址,句柄中則包含了類(lèi)數(shù)據(jù)的地址和實(shí)例數(shù)據(jù)的地址信息。

使用句柄方式最大的好處就是,reference 中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)時(shí)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為),只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而 reference 不需要被修改。

圖片圖片

3.2、直接指針訪問(wèn)

直接指針訪問(wèn),reference 中直接存儲(chǔ)的就是對(duì)象地址,而對(duì)象中存儲(chǔ)了所有的實(shí)例數(shù)據(jù)和類(lèi)數(shù)據(jù)的地址。

使用直接指針?lè)绞剑畲蟮暮锰幘褪撬俣雀欤?jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)。

圖片圖片

就 HotSpot 虛擬機(jī)而言,它使用的是直接指針訪問(wèn)方式來(lái)定位對(duì)象,從其它虛擬機(jī)實(shí)現(xiàn)來(lái)看,使用句柄訪問(wèn)方式也是十分常見(jiàn)的。

四、對(duì)象的內(nèi)存分配原則

在之前的 JVM 內(nèi)存結(jié)構(gòu)布局的文章中,我們介紹到了 Java 堆的內(nèi)存布局,由 年輕代 (Young Generation) 和老年代 (Old Generation) 組成,默認(rèn)情況下按照1 : 2的比例來(lái)分配空間。

其中年輕代又被劃分為三個(gè)不同的區(qū)域:Eden 區(qū)、From Survivor 區(qū)、To Survivor 區(qū),默認(rèn)情況下按照8 : 1 : 1的比例來(lái)分配空間。

Java 堆的內(nèi)存布局,可以用如下圖來(lái)概括。

圖片圖片

當(dāng)創(chuàng)建的對(duì)象不再被使用了是需要被回收掉的,以便騰出空間給新的對(duì)象使用,這就是對(duì)象的垃圾回收,也就是對(duì)象的 GC,我們會(huì)在后續(xù)的文章中再次介紹對(duì)象的垃圾回收算法以及垃圾收集器。

本次我們重點(diǎn)介紹下,創(chuàng)建不同大小的對(duì)象,在堆空間中發(fā)生的內(nèi)存分配變化,以便后續(xù)更好的理解 GC 調(diào)優(yōu)過(guò)程。

4.1、對(duì)象優(yōu)先分配在 Eden 區(qū)

默認(rèn)情況下,創(chuàng)建的對(duì)象會(huì)優(yōu)先分配在年輕代的 Eden 區(qū),當(dāng) Eden 區(qū)不夠用的時(shí)候,會(huì)觸發(fā)一次 Minor GC。

什么是 Minor GC 呢?

Minor GC 指的是 JVM 發(fā)生在年輕代的垃圾回收動(dòng)作,效率高、速度快,但是只清除年輕代的垃圾對(duì)象。

與之對(duì)應(yīng)的還有 Major GC 和 Full GC,Major GC 指的是 JVM 發(fā)生在老年代的垃圾回收動(dòng)作,Major GC 的速度一般要比 Minor GC 慢 10 倍以上;同時(shí),許多 Major GC 是由 Minor GC 引起的,因此把這個(gè)過(guò)程也稱(chēng)之為 Full GC,也就是對(duì)整個(gè)堆進(jìn)行垃圾回收。

當(dāng) Eden 區(qū)滿了以后,會(huì)發(fā)生 Minor GC,存活下來(lái)的對(duì)象會(huì)被移動(dòng)到 Survivor 區(qū),如果 Survivor 區(qū)裝不下這批對(duì)象,此時(shí)會(huì)直接移動(dòng)到老年代。

通常年輕代的對(duì)象存活時(shí)間都很短,在 Minor GC 后,大部分的對(duì)象都會(huì)被回收掉,但是也不排除個(gè)例情況,存活下來(lái)的對(duì)象的年齡會(huì)進(jìn)行 +1,當(dāng)年齡達(dá)到 15歲時(shí),也會(huì)被移動(dòng)到老年代。

用戶可以通過(guò)-XX:MaxTenuringThreshold參數(shù)來(lái)調(diào)整年齡的閥值,默認(rèn)是 15,最大值也是 15。

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

所謂大對(duì)象,顧名思義就是占用內(nèi)存比較多的對(duì)象,大對(duì)象一般可以直接分配到老年代,這是 JVM 的一種優(yōu)化設(shè)計(jì)。

用戶可以手動(dòng)通過(guò)-XX:PretenureSizeThreshold參數(shù)設(shè)置大對(duì)象的大小,默認(rèn)是 0,意味著任何對(duì)象都會(huì)優(yōu)先在年輕代的 Eden 區(qū)分配內(nèi)存。

試想一下,假如大對(duì)象優(yōu)先在 Eden 區(qū)中分配,給其它的對(duì)象預(yù)留的空間就會(huì)變小,此時(shí)很容易觸發(fā) Minor GC,經(jīng)過(guò)多次 GC 之后,大對(duì)象可能會(huì)繼續(xù)存活,最終還是會(huì)被轉(zhuǎn)移到老年代。

與其如此,還不如直接分配到老年代。

4.3、對(duì)象動(dòng)態(tài)年齡判斷機(jī)制

對(duì)象動(dòng)態(tài)年齡判斷,簡(jiǎn)單的說(shuō)就是對(duì) Survivor 區(qū)的對(duì)象年齡從小到大進(jìn)行累加,當(dāng)累加到 X 年齡(某個(gè)年齡)時(shí)占用空間的總和大于 50%,那么比 X 年齡大的對(duì)象都會(huì)移動(dòng)到老年代。

這種機(jī)制是 JVM 的一個(gè)預(yù)測(cè)機(jī)制,虛擬機(jī)并不是完全要求對(duì)象年齡必須達(dá)到 15 才能移動(dòng)到老年代。當(dāng) survivor 區(qū)快要滿了并且存在一批可能會(huì)長(zhǎng)期存活的對(duì)象,那不如提前進(jìn)入老年代,減少年輕代的壓力。

用戶可以使用-XX:TargetSurvivorRatio參數(shù)來(lái)設(shè)置保留多少空閑空間,默認(rèn)值是 50。

4.4、逃逸分析

逃逸分析是一項(xiàng)比較前沿的優(yōu)化技術(shù),它并不是直接優(yōu)化代碼的手段,而是為其它優(yōu)化手段提供了分析技術(shù)。

什么是逃逸分析呢?

當(dāng)一個(gè)對(duì)象在方法里面被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中,這種稱(chēng)為方法逃逸;甚至還有可能被外部線程訪問(wèn)到,譬如賦值給可以在其他線程中訪問(wèn)的實(shí)例變量,這種稱(chēng)為線程逃逸。

如果能證明一個(gè)對(duì)象不會(huì)逃移到方法外或者線程之外,換句話說(shuō)就是別的方法或線程無(wú)法通過(guò)任何途徑訪問(wèn)到這個(gè)對(duì)象,虛擬機(jī)可以通過(guò)一些途徑為這個(gè)變量進(jìn)行一些不同程度的優(yōu)化。

比如棧上分配、同步消除、標(biāo)量替換等優(yōu)化操作。

4.4.1、棧上分配

在上文我們提及到,對(duì)象會(huì)優(yōu)先在堆上分配,垃圾收集器會(huì)定期回收堆內(nèi)存中不再使用的對(duì)象,但這塊的內(nèi)存回收很耗時(shí)。

如果確定一個(gè)對(duì)象不會(huì)逃逸出方法之外,讓這個(gè)對(duì)象在棧上分配,對(duì)象所占用的內(nèi)存空間就可以隨著棧幀出棧而銷(xiāo)毀,這樣垃圾收集器的壓力將會(huì)小很多。

4.4.2、同步消除

線程同步本身是一個(gè)相對(duì)耗時(shí)的操作,如果逃逸分析能夠確定一個(gè)變量不會(huì)逃逸出線程,無(wú)法被其他線程訪問(wèn),那么這個(gè)變量的讀寫(xiě)肯定就不會(huì)有競(jìng)爭(zhēng),此時(shí)虛擬機(jī)會(huì)對(duì)這個(gè)變量,實(shí)施的同步措施進(jìn)行消除,比如去掉同步鎖來(lái)運(yùn)行方法。

4.4.3、標(biāo)量替換

標(biāo)量是指一個(gè)數(shù)據(jù)已經(jīng)無(wú)法再分解成更小的數(shù)據(jù)來(lái)表示了,比如 Java 虛擬機(jī)中的原始數(shù)據(jù)類(lèi)型(int,long 等數(shù)值類(lèi)型以及 reference 類(lèi)型)等都不能進(jìn)一步分解,它們可以稱(chēng)為標(biāo)量。相對(duì)的,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它稱(chēng)為聚合量。

Java 中最典型的聚合量是對(duì)象,如果逃逸分析證明一個(gè)對(duì)象不會(huì)被外部訪問(wèn),并且這個(gè)對(duì)象是可分解的,那程序真正執(zhí)行的時(shí)候?qū)⒖赡懿粍?chuàng)建這個(gè)對(duì)象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來(lái)代替,拆散后的變量便可以被單獨(dú)分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對(duì)象就無(wú)需整體分配空間了。

默認(rèn)情況下逃逸分析是關(guān)閉的,用戶可以使用-XX:+DoEscapeAnalysis參數(shù)來(lái)手動(dòng)開(kāi)啟逃逸分析。

4.5、小結(jié)

綜合以上的內(nèi)容,對(duì)象的內(nèi)存分配流程,可以用如下圖來(lái)概括。

圖片圖片

五、小結(jié)

本文主要從虛擬機(jī)層面,對(duì)對(duì)象的創(chuàng)建過(guò)程,訪問(wèn)方式以及內(nèi)存分配時(shí)的空間變化進(jìn)行了一次知識(shí)整合和總結(jié),如果有描述不對(duì)的地方,歡迎大家留言指出,不勝感激。

六、參考

1.https://zhuanlan.zhihu.com/p/267223891

2.https://zhuanlan.zhihu.com/p/401057707

3.https://www.cnblogs.com/xrq730/p/4827590.html

4.https://www.jianshu.com/p/3d38cba67f8b

6.https://blog.csdn.net/clover_lily/article/details/80095580

7.https://blog.csdn.net/FIRE_TRAY/article/details/51275788

8.https://blog.csdn.net/yb970521/article/details/108015984

責(zé)任編輯:武曉燕 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2020-03-08 16:45:58

數(shù)據(jù)挖掘學(xué)習(xí)數(shù)據(jù)量

2009-11-09 12:55:43

WCF事務(wù)

2024-08-30 08:50:00

2022-02-17 09:24:11

TypeScript編程語(yǔ)言javaScrip

2024-01-16 07:46:14

FutureTask接口用法

2021-04-20 13:59:37

云計(jì)算

2020-06-30 10:45:28

Web開(kāi)發(fā)工具

2024-08-05 09:05:44

2024-07-05 09:31:37

2021-02-03 14:31:53

人工智能人臉識(shí)別

2024-01-12 07:38:38

AQS原理JUC

2017-01-18 15:38:20

語(yǔ)言

2024-09-13 08:49:45

2019-07-18 17:08:56

物聯(lián)網(wǎng)技術(shù)軟件

2024-06-06 08:50:43

2020-07-21 07:42:29

數(shù)據(jù)庫(kù)信息技術(shù)

2015-10-23 17:47:32

BaaSPaaS移動(dòng)中間件

2024-12-06 11:22:27

2024-05-16 11:13:16

Helm工具release

2024-12-18 10:24:59

代理技術(shù)JDK動(dòng)態(tài)代理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 激情五月综合 | 日日日日日日bbbbb视频 | 欧美激情网站 | 久久国产精品视频观看 | 亚洲 欧美 综合 | 草久免费视频 | 91精品国产色综合久久不卡98口 | 在线观看国产视频 | 欧美一级久久 | 免费成人高清在线视频 | 欧美在线观看一区 | 久久久久www | 国产探花在线精品一区二区 | 在线免费观看欧美 | 欧美v在线观看 | 精品在线观看一区 | jlzzjlzz国产精品久久 | 免费久久精品 | 综合精品 | 成人性视频在线 | 久久久久久久久久久爱 | 欧美日韩专区 | 99精品国产一区二区青青牛奶 | 亚洲视频一区在线 | 日韩精品在线一区 | 久久久久久久一区二区三区 | 国产精品国产a级 | 在线成人免费av | 中文字幕视频一区二区 | 97人人澡人人爽91综合色 | 男人的天堂中文字幕 | 91麻豆精品国产91久久久更新资源速度超快 | 成年人国产在线观看 | 亚洲高清在线观看 | 在线一区二区三区 | 国产一区| 欧美久久天堂 | 成人黄色电影免费 | 国产福利91精品 | h视频免费观看 | 在线一区二区国产 |