JVM(七)內(nèi)存與垃圾回收|對(duì)象的實(shí)例化內(nèi)存布局與訪問定位+直接內(nèi)存

本文主要講對(duì)象相關(guān)(對(duì)象實(shí)例化、內(nèi)存布局、訪問定位)和直接內(nèi)存相關(guān)的內(nèi)容。

目錄
?1 對(duì)象的實(shí)例化內(nèi)存布局與訪問定位
??1.1 對(duì)象的實(shí)例化
???1.1.1 創(chuàng)建對(duì)象的方式
???1.1.2 創(chuàng)建對(duì)象的步驟
????1.1.2.1判斷對(duì)象對(duì)應(yīng)的類是否加載、鏈接、初始化
????1.1.2.2 為對(duì)象分配內(nèi)存
????1.1.2.3 處理并發(fā)安全問題
????1.1.2.4 初始化分配到的空間
????1.1.2.5 設(shè)置對(duì)象的對(duì)象頭
????1.1.2.6 執(zhí)行init方法進(jìn)行初始化
??1.2 對(duì)象的內(nèi)存布局
??1.3 對(duì)象的訪問定位
?2 直接內(nèi)存


1 對(duì)象的實(shí)例化內(nèi)存布局與訪問定位


1.1 對(duì)象的實(shí)例化

1.1.1 創(chuàng)建對(duì)象的方式
對(duì)象實(shí)例化方式
1.1.2 創(chuàng)建對(duì)象的步驟
創(chuàng)建對(duì)象的步驟
1.1.2.1 判斷對(duì)象對(duì)應(yīng)的類是否加載、鏈接、初始化
  • 虛擬機(jī)遇到一條new指令,首先去檢查這個(gè)指令的參數(shù)能否在Metaspace的常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已經(jīng)被加載、解析和初始化。( 即判斷類元信息是否存在)。
  • 如果沒有,那么在雙親委派模式下,使用當(dāng)前類加載器以ClassLoader+包名+類名為Key查找對(duì)應(yīng)的.class文件。如果沒有找到文件,則拋出ClassNotFoundException異常,如果找到,則進(jìn)行類加載,并生成對(duì)應(yīng)的Class類對(duì)象
1.1.2.2 為對(duì)象分配內(nèi)存
  • 首先計(jì)算對(duì)象占用空間大小,接著在堆中劃分一塊內(nèi)存給新對(duì)象。

  • 如果實(shí)例成員變量是引用變量,僅分配引用變量空間即可,即4個(gè)字節(jié)大小。

  • 如果內(nèi)存是規(guī)整的,那么虛擬機(jī)將采用的是指針碰撞(BumpThePointer)來為對(duì)象分配內(nèi)存。意思是所有用過的內(nèi)存在一邊,空閑的內(nèi)存在另外一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存就僅僅是把指針向空閑那邊挪動(dòng)一段與對(duì)象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基于標(biāo)記壓縮算法的,虛擬機(jī)采用這種分配方式。一般使用帶有compact (整理)過程的收集器時(shí),使用指針碰撞。

    指針碰撞

  • 如果內(nèi)存不是規(guī)整的,已使用的內(nèi)存和未使用的內(nèi)存相互交錯(cuò),那么虛擬機(jī)將采用的是空閑列表(Free List)來為對(duì)象分配內(nèi)存。意思是虛擬機(jī)維護(hù)了一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,再分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的內(nèi)容

說明:選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定

1.1.2.3 處理并發(fā)安全問題

在分配內(nèi)存空間時(shí),要保證new對(duì)象時(shí)候的線程安全性:創(chuàng)建對(duì)象是非常頻繁的操作,虛擬機(jī)需要解決并發(fā)問題。虛擬機(jī)采用 了兩種方式解決并發(fā)問題:

  • CAS ( Compare And Swap )失敗重試、區(qū)域加鎖:保證指針更新操作的原子性;

CAS原理分析

  • 本地線程分配緩沖區(qū)(TLAB ,Thread Local Allocation Buffer),把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存。虛擬機(jī)是否使用TLAB,可以通過-XX:+/-UseTLAB參數(shù)來 設(shè)定。
1.1.2.4 初始化分配到的空間

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

給對(duì)象的屬性賦值的操作:
① 屬性的默認(rèn)初始化
② 顯式初始化
③ 代碼塊中初始化
④ 構(gòu)造器中初始化
①是在初始化分配到的空間這一步做的,而②③④是在執(zhí)行init方法進(jìn)行初始化做的

1.1.2.5 設(shè)置對(duì)象的對(duì)象頭

將對(duì)象的所屬類(即類的元數(shù)據(jù)信息)、對(duì)象的HashCode和對(duì)象的GC信息、鎖信息等數(shù)據(jù)存儲(chǔ)在對(duì)象的對(duì)象頭中。這個(gè)過程的具體設(shè)置方式取決于JVM實(shí)現(xiàn)。

1.1.2.6 執(zhí)行init方法進(jìn)行初始化
  • 在Java程序的視角看來,初始化才正式開始。初始化成員變量,執(zhí)行實(shí)例化代碼塊,調(diào)用類的構(gòu)造方法,并把堆內(nèi)對(duì)象的首地址賦值給引用變量。
  • 因此一般來說,new指令之 后會(huì)接著執(zhí)行方法(由字節(jié)碼中是否跟隨有invokespecial指令所決定),把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全創(chuàng)建出來。
/**
 * 測(cè)試對(duì)象實(shí)例化的過程 example01
 *  ① 加載類元信息 - ② 為對(duì)象分配內(nèi)存 - ③ 處理并發(fā)問題  - ④ 屬性的默認(rèn)初始化(零值初始化)
 *  - ⑤ 設(shè)置對(duì)象頭的信息 - ⑥ 屬性的顯式初始化、代碼塊中初始化、構(gòu)造器中初始化
 *
 *
 *  給對(duì)象的屬性賦值的操作:
 *  ① 屬性的默認(rèn)初始化 - ② 顯式初始化 / ③ 代碼塊中初始化 - ④ 構(gòu)造器中初始化
 */
public class Customer{
    int id = 1001;
    String name;
    Account acct;

    {
        name = "匿名客戶";
    }
    public Customer(){
        acct = new Account();
    }
}
class Account{
}

1.2 對(duì)象的內(nèi)存布局

對(duì)象內(nèi)存布局

對(duì)齊填充對(duì)齊填充是用于確保對(duì)象的內(nèi)存的總長(zhǎng)度為8字節(jié)的整數(shù)倍
為什么要是確保是8字節(jié)的整數(shù)倍呢?
??因?yàn)閔otspot要求對(duì)象起始地址為8字節(jié)的整數(shù)倍以便于自動(dòng)內(nèi)存管理,換句話說,對(duì)象的總長(zhǎng)度要為8字節(jié)的整數(shù)倍才能保證如此。而又因?yàn)閷?duì)象頭正好是8字節(jié)(32位或64位)的整數(shù)倍,但是實(shí)例數(shù)據(jù)長(zhǎng)度是任意的,因此需要對(duì)齊補(bǔ)充來確保整個(gè)對(duì)象總長(zhǎng)度為8字節(jié)的整數(shù)倍
Java對(duì)象內(nèi)存布局對(duì)齊填充等價(jià)形式推導(dǎo)

/**
 * example02
 */
public class CustomerTest {
    public static void main(String[] args) {
        Customer cust = new Customer();
    }
}
example01 & example02的圖解

1.3 對(duì)象的訪問定位

①JVM是如何通過棧幀中的對(duì)象引|用訪問到其內(nèi)部的對(duì)象實(shí)例的呢?
定位,通過棧上reference訪問

對(duì)象的訪問定位

②對(duì)象訪問的主要方式有幾種?

  • 句柄訪問

    句柄訪問

  • 直接指針(HotSpot采用)

    直接指針

③對(duì)象訪問兩種方式的優(yōu)點(diǎn)?

對(duì)象訪問方式 優(yōu)點(diǎn)
句柄訪問 對(duì)象移動(dòng)(垃圾收集對(duì)象移動(dòng)很普遍)時(shí)改變句柄到對(duì)象實(shí)例的指針即可
直接指針 節(jié)省空間;效率快

2 直接內(nèi)存(Direct Memory)


  • 不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機(jī)規(guī)范》中定義的內(nèi)存區(qū)域
  • 直接內(nèi)存是Java堆外的、直接向系統(tǒng)申請(qǐng)的內(nèi)存區(qū)間
  • 來源于NIO,通過存在堆中的DirectByteBuffer操作Native內(nèi)存
/**
 *  IO                  NIO (New IO / Non-Blocking IO)
 *  byte[] / char[]     Buffer
 *  Stream              Channel
 *
 * 查看直接內(nèi)存的占用與釋放 example03
 */
public class BufferTest {
    private static final int BUFFER = 1024 * 1024 * 1024;//1GB

    public static void main(String[] args){
        //直接分配本地內(nèi)存空間
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
        System.out.println("直接內(nèi)存分配完畢,請(qǐng)求指示!");

        Scanner scanner = new Scanner(System.in);
        scanner.next();

        System.out.println("直接內(nèi)存開始釋放!");
        byteBuffer = null;
        System.gc();
        scanner.next();
    }
}
  • 通常,訪問直接內(nèi)存的速度會(huì)優(yōu)于Java堆。即讀寫性能高
    ??a.因此出于性能考慮,讀寫頻繁的場(chǎng)合可能會(huì)考慮使用直接內(nèi)存
    ??b.Java的NIO庫允許Java程序使用直接內(nèi)存,用于數(shù)據(jù)緩沖區(qū)



  • 也可能導(dǎo)致OutOfMemoryError異常:OutOfMemoryError: Direct buffer memory

/**
 * 本地內(nèi)存的OOM:  OutOfMemoryError: Direct buffer memory  example04
 */
public class BufferTest2 {
    private static final int BUFFER = 1024 * 1024 * 20;//20MB

    public static void main(String[] args) {
        ArrayList<ByteBuffer> list = new ArrayList<>();

        int count = 0;
        try {
            while(true){
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
                list.add(byteBuffer);
                count++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            System.out.println(count);
        }
    }
}
  • 由于直接內(nèi)存在Java堆外,因此它的大小不會(huì)直接受限于一Xmx指定的最大
    堆大小,但是系統(tǒng)內(nèi)存是有限的,Java堆和直接內(nèi)存的總和依然受限于操作系統(tǒng)能給出的最大內(nèi)存。

  • 缺點(diǎn)
    ??a.分配回收成本較高
    ??b.不受JVM內(nèi)存回收管理

  • 直接內(nèi)存大小可以通過MaxDirectMemorySize設(shè)置

  • 如果不指定,默認(rèn)與堆的最大值-Xmx參數(shù)值一致

  • 簡(jiǎn)單理解:java process memory = java heap + native memory

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377