Java多線程高併發(二) java內存佈局

一  java對象創建

       對象創建底層原理圖:

本文主要是針對內存佈局的概述,如果想要對詳細的java對象創建過程感興趣的話:可以參考這篇博客https://blog.csdn.net/justloveyou_/article/details/72466416

二 java內存存儲

1、對象在內存中存儲的佈局分爲三塊

  • 對象頭

    • 存儲對象自身的運行時數據:Mark Word(在32bit和64bit虛擬機上長度分別爲32bit和64bit),包含如下信息:
      • 對象hashCode
      • 對象GC分代年齡
      • 鎖狀態標誌(輕量級鎖、重量級鎖)
      • 線程持有的鎖(輕量級鎖、重量級鎖)
      • 偏向鎖相關:偏向鎖、自旋鎖、輕量級鎖以及其他的一些鎖優化策略是JDK1.6加入的,這些優化使得Synchronized的性能與ReentrantLock的性能持平,在Synchronized可以滿足要求的情況下,優先使用Synchronized,除非是使用一些ReentrantLock獨有的功能,例如指定時間等待等。(在32位系統佔4字節,在64位系統中佔8字節;)
    • Class Pointer:對象指向類元數據的指針(32bit-->32bit,64bit-->64bit(未開啓壓縮指針),32bit(開啓壓縮指針))                                也就是說在未開啓壓縮指針的情況下,在64位系統中佔8字節;開啓壓縮指針那麼就是4個字節
      • JVM通過這個指針來確定這個對象是哪個類的實例(根據對象確定其Class的指針)

    • Length::如果是數組對象,還有一個保存數組長度的空間,佔4個字節;

  • 對象實際數據 

              對象實際數據包括了對象的所有成員變量,其大小由各個成員變量的大小決定,比如:byte和boolean是1個字節,short和          char是2個字節,int和float是4個字節,long和double是8個字節,reference是4個字節(64位系統中是8個字節)。

  • Primitive Type Memory Required(bytes)
    boolean 1
    byte 1
    short 2
    char 2
    int 4
    float 4
    long 8
    double 8

    對於reference類型來說,在32位系統上佔用4bytes, 在64位系統上佔用8bytes。

  • 對齊填充

       Java對象佔用空間是8字節對齊的,即所有Java對象佔用bytes數必須是8的倍數。例如,一個包含兩個屬性的對象:int和byte,這個對象需要佔用8+4+1=13個字節,這時就需要加上大小爲3字節的padding進行8字節對齊,最終佔用大小爲16個字節。

2.分析內存佈局

針對上面的結論,如何進行驗證呢?可以通過jol來查看。

導入maven的openjdk.jol包

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

添加-XX:+PrintCommandLineFlags參數   

2.1  創建Object對象

  Object object =new Object();
  System.out.println(ClassLayout.parseInstance(object).toPrintable());

打印結果:

-XX:InitialHeapSize=132141888 -XX:MaxHeapSize=2114270208 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
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 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

註明:使用的是jdk1.8 64位(默認開啓壓縮指針)

-XX:+UseCompressedClassPointers 開啓類指針壓縮

-XX:+UseCompressedOops :開啓普通對象指針壓縮

可以看到markword佔8個字節,class Pointer佔4個字節,然後12不被8整除,補齊4位也就是object佔用16個字節

關於這兩個指針壓縮的關係,我這邊做了個測試

1.開啓對象指針壓縮:-XX:+UseCompressedOops  ( markword 8字節,class Pointer 4字節)

2.關閉對象指針壓縮:-XX:-UseCompressedOops  (markword 8字節,class Pointer 8 字節)

3.開啓類指針壓縮:-XX:+UseCompressedClassPointers (markword 8字節,class Pointer 4字節)

4.關閉類指針壓縮:-XX:-UseCompressedClassPointers(markword 8字節,class Pointer 8 字節)

我們發現任何一個單獨的類指針或者對象指針的開啓跟關閉,都會直接影響結果。得出結論,單獨開啓一個指針其對應的指針也會一起開啓

5.開啓類指針,關閉對象指針:-XX:+UseCompressedClassPointers -XX:-UseCompressedOops(markword 8字節,class Pointer 8 字節)

6.關閉類指針,開啓對象指針:  -XX:-UseCompressedClassPointers -XX:+UseCompressedOops(markword 8字節,class Pointer 8 字節)

7.開啓類指針,關閉對象指針:-XX:+UseCompressedClassPointers -XX:-UseCompressedOops(Java HotSpot(TM) 64-Bit Server VM warning: UseCompressedClassPointers requires UseCompressedOops)

更多的指針壓縮可以參考這篇文章:https://blog.csdn.net/qq_33223299/article/details/106354718

2.2  創建對象(基本數據類型)

Person類:

public class People {

    private   int  id;

    private  char sex;

    static  private int age;

}

測試:

        People people=new People();
        System.out.println(ClassLayout.parseInstance(people).toPrintable());

結果:

 

指針壓縮情況下:對象頭12字節+int 4個字節+char 2個字節+對齊填充 6個字節=24個字節

結論:當對象屬性是基本數據類型的時候,那麼該對象的大小等於對象頭size+基本數據類型屬性size+對齊填充size

我們發現加static修飾的屬性並沒有分配在堆內存中,所以在內存佈局中並沒有看到

2.3 數組類型

        long[] longs=new long[5];
        System.out.println(ClassLayout.parseInstance(longs).toPrintable());

結果:

 

64位系統中,數組對象的對象頭佔用24 bytes,啓用壓縮後佔用16字節。比普通對象佔用內存多是因爲需要額外的空間存儲數組的長度。基礎數據類型數組佔用的空間包括數組對象頭以及基礎數據類型數據佔用的內存空間。由於對象數組中存放的是對象的引用,對象頭 16字節+long的大小 8個字節*數量 5=56個字節

但是本人測試了對象數組

2.4 包裝類型

包裝類(Boolean/Byte/Short/Character/Integer/Long/Double/Float)佔用內存的大小等於對象頭大小加上底層基礎數據類型的大小。

包裝類型的Retained Size佔用情況如下:

Numberic Wrappers +useCompressedOops -useCompressedOops
Byte, Boolean 16 bytes 24 bytes
Short, Character 16 bytes 24 bytes
Integer, Float 16 bytes 24 bytes
Long, Double 24 bytes 24 bytes


結論:包裝類型實際數據大小與基礎數據類型一致

2.5 String類型

在JDK1.7及以上版本中,java.lang.String中包含2個屬性,一個用於存放字符串數據的char[], 一個int類型的hashcode, 部分源代碼如下:

在關閉指針壓縮的時候:

內存大小=對象頭大小16字節+int類型大小4字節+數組引用大小8字節+padding4字節=32字節;

 

開啓壓縮指針:

 

內存大小=對象頭大小12字節+int類型大小4字節+數組引用大小4字節+padding4字節=24字節;

參考文章:https://www.jianshu.com/p/91e398d5d17c

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章