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

原來New關鍵字創(chuàng)建對象的背后還隱藏了這么多秘密,看完這篇文章我頓悟了

開發(fā) 前端
在64位平臺的HotSpot中使用32位指針(實際存儲用64位),內存使用會多出1.5倍左右,使用較大指針在主內存和緩存之間移動數(shù)據(jù),占用較大寬帶,同時GC也會承受較大壓力。

前言

對于前面幾篇文章, 主要就是說明了一個.java文件是如何一步步編譯, 解析最后加載到JVM中運行的, 那么本篇文章將說明對象是如何創(chuàng)建的, 包括創(chuàng)建過程、對象頭與指針壓縮、jvm對象內存分配詳解、逃逸分析,線上分配,標量替換等等內容。

內容有點多,所以準備分為三篇文章來寫:

  • JVM對象創(chuàng)建及對象大小與指針壓縮
  • 對象內存分配
  • 對象內存回收

如果感覺文章中有的圖片字太小不清楚的可以通過公眾號加我,然后說明是哪篇文章的圖片,然后我發(fā)給你。

對象的創(chuàng)建

對象創(chuàng)建的主要流程:

圖片圖片

1.類加載檢查

虛擬機遇到一條new指令時,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應的類加載過程。new指令對應到語言層面上講是,new關鍵詞、對象克隆、對象序列化等。

對于我們來說,我們寫的java代碼是new 一個對象,實際上對于底層jvm實際上是執(zhí)行了一個new 指令。

這里用的插件是:jclasslib Bytecode Viewer

圖片圖片

首先會判斷這個類有沒有被加載過,如果沒有加載過,那么它首先會執(zhí)行加載類的過程(前幾篇文章有講),如果加載過了,那么就要開始new對象了,這個對象一般來說可能放在堆中也有可能放在棧里邊,但是不管放在哪,前提都是需要分配一塊內存空間的。

2.分配內存

在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需內存的大小在類 加載完成后便可完全確定,為對象分配空間的任務等同于把 一塊確定大小的內存從Java堆中劃分出來。

這個步驟有兩個問題:

  • 如何劃分內存。
  • 在并發(fā)情況下, 可能出現(xiàn)正在給對象A分配內存,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內存的情況。

劃分內存的方法:

“指針碰撞”(Bump the Pointer)(默認用指針碰撞)

如果Java堆中內存是絕對規(guī)整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器,比如下圖中藍色實線表示當前指針位置,虛線表示挪動后的位置,那所謂分配內存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離。

圖片圖片

“空閑列表”(Free List)

如果Java堆中的內存并不是規(guī)整的,已使用的內存和空 閑的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例, 并更新列表上的記錄。

圖片圖片

但是具體使用的是指針碰撞的方式還是使用的是空閑列表的分配方式,取決于使用的什么垃圾回收算法,如果使用的是標記整理的話,那么最終剩余的內存肯定是第一種,那么使用的也就是指針碰撞的方式,如果使用的是標記清除的話,那么最終剩余的內存肯定是第二種,所以就使用空閑列表的方式來分配內存。

解決并發(fā)問題的方法:

不管使用哪種方式分配,都會出現(xiàn)并發(fā)問題,也就是兩個線程同時創(chuàng)建了一個對象,然后爭搶同一塊內存

圖片圖片

多個線程創(chuàng)建了多個對象,但是內存空間只有一塊,那么jvm為了解決這種并發(fā)問題,采取了以下兩種措施

CAS(compare and swap)

虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性來對分配內存空間的動作進行同步處理。

CAS配上失敗重試也就是線程A和線程B同時爭搶這一塊內存,如果線程A先爭搶到了這塊內存,那么線程B重新進行分配,發(fā)現(xiàn)這塊內存分配給了線程A,然后就會在這塊內存后面進行內存分配操作。這樣線程A、B對象的內存空間就在并發(fā)的情況下被分配了。

本地線程分配緩沖(TLAB: Thread Local Allocation Buffer)

把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中(比如Eden區(qū))預先分配一小塊內存。通過-XX:+/-UseTLAB參數(shù)來設定虛擬機是否使用TLAB(JVM會默認開啟-XX:+UseTLAB)

那么這個內存也不可能特別大,好像默認是Eden區(qū)的1% , 通過-XX:TLABSize 可以指定TLAB大小。如果這個時候放不下了,那么就會恢復CAS配上失敗重試的方式進行分配。當然,一般不推薦你去改JVM默認的參數(shù)設置。

圖片圖片

線程A和線程B在Eden區(qū)預先分配一塊屬于自己的內存空間,然后把各自的對象放到各自的空間種。JDK8默認使用的就是這種方式。

對象的分配過程會在下一篇文章詳細說明。

3.初始化零值

內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭), 如果使用TLAB,這一工作過程也可以提前至TLAB分配時進行。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應的零值。

也就是對于對象的成員變量,比如int initData = 666;那么在這個過程,會先給initData 賦一個0值,就和前面有一篇文章中提到過靜態(tài)變量的初始化賦值是一樣的。最終可能有一步會把真正的值666賦給initData。

4.設置對象頭

初始化零值之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭Object Header之中。

在HotSpot虛擬機中,對象在內存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、 實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。HotSpot虛擬機的對象頭包括兩部分信息,第一部分用于存儲對象自身的運行時數(shù)據(jù), 如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時 間戳等。對象頭的另外一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

32位對象頭

圖片圖片

對象頭中有一個Mark Word標記字段,第一列是對象的一個狀態(tài),可能有一些對象被加鎖了或者是被GC標記了,不同的對象,它對象頭的結構是不一樣的,比如說一個對象是正常的對象,也就是沒有任何的鎖,對象頭中前面25bit存儲是對象的hashCode,中間4bit存儲的是對象的分代年齡,分代年齡在上一篇文章中有講過,它是4bit,也就以為著它的分代年齡是<=15的,因為4位(bit)大小可以表示從0到15的數(shù)值,因此無法存儲大于15的數(shù)值,當然還有一些偏向鎖、鎖標志位等鎖的標記。關于鎖的相關內容也會在后面寫并發(fā)相關的文章的時候進行詳解。

還有一塊就是Klass Pointer類型指針,一個對象new出來之后是放到堆中的,但是在這個對象的頭部區(qū)域,有一個指針,指向方法區(qū)的對象所屬的類的元數(shù)據(jù)信息。如下圖中畫紅線的地方的示例。

圖片圖片

比如說就是下面這一段代碼,要想在元數(shù)據(jù)區(qū)找到compute方法對應的代碼,就是通過這個類型指針Klass Pointer去找。

圖片圖片

那么還有一個對象叫做類對象,比如Math類所屬的對象mathClass,這個對象是放在堆中的。

圖片圖片

堆中的這個mathClass對象和元數(shù)據(jù)區(qū)的Math.class是什么關系呢?            

Math.class是類的元數(shù)據(jù)信息,也就是我們編寫的代碼,那么mathClass是類裝載完之后,是jvm給我們開發(fā)人員在我們想訪問類的元數(shù)據(jù)信息是提供的一個對象,我們可以通過這個對象mathClass去訪問類的元數(shù)據(jù)信息,簡單一點就是反射,通過反射是可以獲取到很多信息的,類的名稱。方法的名稱等等。但是mathClass對象中是不會存儲這些代碼的,代碼只是存儲在方法區(qū)。

這個是jvm提供給我們開發(fā)人員去使用的,但是jvm內部不會這么干,而是通過剛剛講的類型指針。而元數(shù)據(jù)信息的存儲介質是C++對象,這個類型指針也是C++實現(xiàn)的。

還有一塊就是數(shù)組的長度,如果是一個數(shù)組對象的話,對象頭中還有一塊會存儲數(shù)組的長度。

64位對象頭

圖片圖片

對象頭在hotspot的C++源碼markOop.hpp文件里的注釋如下:

// Bit-format of an object header (most significant first, big endian layout below):
//
//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

5.執(zhí)行方法

執(zhí)行方法,即對象按照程序員的意愿進行初始化。對應到語言層面上講,就是為屬性賦值(注意,這與上面的賦零值不同,這是由程序員賦的值),和執(zhí)行構造方法。

這一步的話比如就會把initData賦值為666, 因為在初始化零值這個步驟中initData被賦值為0,這一步可以說是真正的進行賦值。也就是下圖中框起來的部分,這個過程是C++調用的。

圖片圖片

對象大小與指針壓縮

對象大小可以用jol-core包查看,引入依賴

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

以下這幾行代碼的話主要就是想查看new Object() 以及new int[]{}還有new A()對象的大小。

package com.liuxs.fusionx;


import org.openjdk.jol.info.ClassLayout;


/**
 * @author: Liu Yuehui
 * @ClassName: JOLSample
 * @date: 2023/11/27 0:25
 * @description: 查看對象大小
 * @version:v1:2023/11/27 0:25:
 **/
public class JOLSample {


    public static void main(String[] args) {
        ClassLayout layout = ClassLayout.parseInstance(new Object());
        System.out.println(layout.toPrintable());


        System.out.println();
        ClassLayout layout1 = ClassLayout.parseInstance(new int[]{});
        System.out.println(layout1.toPrintable());


        System.out.println();
        ClassLayout layout2 = ClassLayout.parseInstance(new A());
        System.out.println(layout2.toPrintable());
    }


    // -XX:+UseCompressedOops           默認開啟的壓縮所有指針
    // -XX:+UseCompressedClassPointers  默認開啟的壓縮對象頭里的類型指針Klass Pointer
    // Oops : Ordinary Object Pointers
    public static class A {
        //8B mark word
        //4B Klass Pointer   如果關閉壓縮-XX:-UseCompressedClassPointers或-XX:-UseCompressedOops,則占用8B
        int id;        //4B
        String name;   //4B  如果關閉壓縮-XX:-UseCompressedOops,則占用8B
        byte b;        //1B
        Object o;      //4B  如果關閉壓縮-XX:-UseCompressedOops,則占用8B
    }
}

運行結果:

圖片圖片

Object對象大概是可以分為以下幾塊

圖片圖片

這里的類型指針只占了4個字節(jié)是因為64位系統(tǒng)默認是8字節(jié),但是會涉及到指針壓縮,壓縮之后就是4字節(jié)。

這里有一個叫對象對齊,也就是上面說到對象頭的第三塊對齊填充(Padding),這塊部分有的時候有,有的時候沒有,也就是jvm內部會把內存的讀取信息按照8個字節(jié)對齊,這個是整個jvm底層包括計算機組成原理經(jīng)過大量實踐證明的,也就是通過8個字節(jié)的對象的對齊,會讓整個計算機的存取效率非常之高。

比如我這個操作系統(tǒng)是64位的,它的內存大概是一格一格的,比如下面這張圖,一共就是64位,現(xiàn)在有個對象只占一點空間,你在查這個對象的時候,還要評估這個對象的大小,然后從這個大小的起始位置去偏移,這就比較麻煩了,那么8個字節(jié)的存取說白了就是對象尋址最優(yōu)的一種方式。

圖片圖片

比如Object對象中,Mark Word標記字段和Klass pointer類型指針占了12字節(jié),也就是這個Object對象真正的大小是12字節(jié),但是為了滿足對象對齊是8的整數(shù)倍,所以有搞了4個字節(jié)的對齊,這樣就成了16字節(jié),也就是8的2倍。讓我們對象總共的大小是16字節(jié)。

圖片圖片

數(shù)組對象會多一個數(shù)組長度。

圖片圖片

A對象

其它內容一樣,這里就不過多贅述了,這里的bate類型的b只占用了1字節(jié),但是會有內部對齊,對齊成為了4字節(jié),然后Object對象只占用了4字節(jié),就是因為Object對象存儲的是指針,只占了4個字節(jié)是因為64位系統(tǒng)默認是8字節(jié),但是會涉及到指針壓縮,壓縮之后就是4字節(jié)。

圖片圖片

什么是Java對象的指針壓縮?

現(xiàn)象

圖片圖片

對于上面查看對象大小的代碼先在IDEA中設置一些jvm的參數(shù)。

XX:-UseCompressedOops禁止指針壓縮

運行結果

圖片圖片

可以發(fā)現(xiàn)對象對齊沒有了,但是多了一個對象頭,也就是說會有兩個4字節(jié)大小的位置來存儲類型指針。包括A類中name和Object對象也都是8字節(jié),這些對象都是放在堆中的,如果不開啟指針壓縮,會無形的增大很多空間,會導致整個堆的壓力非常大,很容易就放滿,然后GC...

1.jdk1.6 update14開始,在64bit操作系統(tǒng)中,JVM支持指針壓縮。

2.jvm配置參數(shù):UseCompressedOops,compressed--壓縮、oop(ordinary object pointer)--對象指針。

3.啟用指針壓縮:-XX:+UseCompressedOops(默認開啟),禁止指針壓縮:-XX:-UseCompressedOops。

為什么要進行指針壓縮?

1.在64位平臺的HotSpot中使用32位指針(實際存儲用64位),內存使用會多出1.5倍左右,使用較大指針在主內存和緩存之間移動數(shù)據(jù),占用較大寬帶,同時GC也會承受較大壓力。

2.為了減少64位平臺下內存的消耗,啟用指針壓縮功能。

3.在jvm中,32位地址最大支持4G內存(2的32次方),但是現(xiàn)在的機器基本都是64位的,也就是2的64次方,這絕對是一個非常大的數(shù)字,也就是64位能表述的內存非常大,可以通過對對象指針的存入堆內存時壓縮編碼、取出到cpu寄存器后解碼方式進行優(yōu)化(對象指針在堆中是32位,在寄存器中是35位,2的35次方=32G),使得jvm只用32位地址就可以支持更大的內存配置(小于等于32G)。

4.堆內存小于4G時,不需要啟用指針壓縮,jvm會直接去除高32位地址,即使用低虛擬地址空間。

5.堆內存大于32G時,壓縮指針會失效,會強制使用64位(即8字節(jié))來對java對象尋址,這就會出現(xiàn)1的問題,所以堆內存不要大于32G為好。

說的簡單一點就是如果壓縮了,只占用4個字節(jié),如果沒有壓縮占用8個字節(jié),是為了節(jié)約內存空間。

責任編輯:武曉燕 來源: 猿人劉先生
相關推薦

2021-02-24 07:38:50

Redis

2025-03-28 08:53:51

2017-08-09 15:07:08

大數(shù)據(jù)數(shù)據(jù)分析戶畫像

2015-12-02 18:11:06

百度地圖/地圖軟件

2019-05-30 09:32:49

2024-11-19 18:03:04

2017-03-10 21:04:04

Android適配

2020-04-07 19:16:31

微信隱藏功能移動應用

2018-06-26 15:00:24

Docker安全風險

2017-03-07 15:35:26

Android適配 界面

2021-04-23 10:01:19

JavaScript 關鍵字對象

2022-05-27 08:18:00

HashMapHash哈希表

2020-11-12 10:37:29

微服務

2021-01-14 05:08:44

編譯鏈接

2022-05-29 08:54:44

Edge瀏覽器

2024-12-31 00:05:24

new?關鍵字C#

2021-06-14 07:23:42

Windows10操作系統(tǒng)微軟

2019-05-21 15:06:53

微信長按功能分享

2020-12-18 13:35:41

人工智能機器學習算法

2019-07-10 15:15:23

JVM虛擬機Java
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品国产乱码久久久久久中文 | 成人精品国产 | 日韩精品一区二区三区中文在线 | 欧美成人影院 | 国产精品明星裸体写真集 | 免费一区二区 | www97影院| 久久成人免费视频 | 亚洲一区二区三区免费在线 | 伊人伊人| 一区视频 | 日日夜夜草 | 国产在线视频三区 | 日韩av一区二区在线 | 日韩免费在线观看视频 | 国产一区二区在线免费观看 | 国产伦精品 | 亚洲91精品| 亚洲高清中文字幕 | 午夜精品一区二区三区在线视频 | 久久亚洲国产精品 | 国产一区二区精品在线观看 | 欧美一区二区三区久久精品 | 精品久久一区 | 国产一区二区三区四区在线观看 | 国产日韩精品视频 | 一区二区在线看 | 在线毛片网 | 亚洲综合区 | 久久性| 一区二区国产在线观看 | 国产精品久久久久aaaa | 综合欧美亚洲 | 国产线视频精品免费观看视频 | 天天射影院 | 每日更新av | 久久久精品一区 | 91精品国产91久久久久游泳池 | 日本不卡一区 | 国产精品福利在线观看 | 免费在线看黄 |