精华内容
下载资源
问答
  • 概述对象实例由对象头、实例...| 类型 | 32位JVM | 64位JVM|| ------ ---- | ------------| --------- || markword | 32bit | 64bit || 类型指针 | 32bit |64bit ,开启指针压缩时为32bit || 数组长度 | 32bit |3...

    概述

    对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度;

    | 类型 | 32位JVM | 64位JVM|

    | ------ ---- | ------------| --------- |

    | markword | 32bit | 64bit |

    | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit |

    | 数组长度 | 32bit |32bit |

    76e3f7bed1fe566262d842666ecae3a7.png

    header.png

    9c6790e6d5b34dcdd25cfcbf48491392.png

    compressed_header.png

    可以看到

    开启指针压缩时,markword占用8bytes,类型指针占用8bytes,共占用16bytes;

    未开启指针压缩时,markword占用8bytes,类型指针占用4bytes,但由于java内存地址按照8bytes对齐,长度必须是8的倍数,因此会从12bytes补全到16bytes;

    数组长度为4bytes,同样会进行对齐,补足到8bytes;

    另外从上面的截图可以看到,开启指针压缩之后,对象类型指针为0xf800c005,但实际的类型指针为0x7c0060028;那么指针是如何压缩的呢?

    实际上由于java地址一定是8的倍数,因此将0xf800c005*8即可得到实际的指针0x7c0060028,关于指针压缩的更多知识可参考官方文档。

    markword结构

    markword的结构,定义在markOop.hpp文件:

    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)

    [ptr | 00] locked ptr points to real header on stack

    [header | 0 | 01] unlocked regular object header

    [ptr | 10] monitor inflated lock (header is wapped out)

    [ptr | 11] marked used by markSweep to mark an object

    由于目前基本都在使用64位JVM,此处不再对32位的结构进行详细说明:

    偏向锁标识位

    锁标识位

    锁状态

    存储内容

    0

    01

    未锁定

    hash code(31),年龄(4)

    1

    01

    偏向锁

    线程ID(54),时间戳(2),年龄(4)

    00

    轻量级锁

    栈中锁记录的指针(64)

    10

    重量级锁

    monitor的指针(64)

    11

    GC标记

    空,不需要记录信息

    此处,有几点要注意:

    如果对象没有重写hashcode方法,那么默认是调用os::random产生hashcode,可以通过System.identityHashCode获取;os::random产生hashcode的规则为:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存储;另外一旦生成了hashcode,JVM会将其记录在markword中;

    GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15;

    当处于轻量级锁、重量级锁时,记录的对象指针,根据JVM的说明,此时认为指针仍然是64位,最低两位假定为0;当处于偏向锁时,记录的为获得偏向锁的线程指针,该指针也是64位;

    We assume that stack/thread pointers have the lowest two bits cleared.

    ObjectMonitor* monitor() const {

    assert(has_monitor(), "check");

    // Use xor instead of &~ to provide one extra tag-bit check.

    return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右两位为10,因此异或之后最右两位为0

    }

    JavaThread* biased_locker() const {

    assert(has_bias_pattern(), "should not call this otherwise");

    return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));

    //~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)为11111111111111111111110010000000,计算后的结果中,低10位全部为0;

    }

    由于java中内存地址都是8的倍数,因此可以理解为最低3bit为0,因此假设轻量级和重量级锁的最低2位为0是成立的;但为什么偏向锁的最低10位都是0?查看markOop.hpp文件,发现有这么一句话:

    // Alignment of JavaThread pointers encoded in object header required by biased locking

    enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits)

    //epoch_shift+epoch_bits=10

    };

    thread.hpp中重载了operator new:

    void* operator new(size_t size) { return allocate(size, true); }

    // ======= Thread ========

    // Support for forcing alignment of thread objects for biased locking

    void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) {

    if (UseBiasedLocking) {

    const int alignment = markOopDesc::biased_lock_alignment;//10

    size_t aligned_size = size + (alignment - sizeof(intptr_t));

    void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC)

    : os::malloc(aligned_size, flags, CURRENT_PC);

    void* aligned_addr = (void*) align_size_up((intptr_t) real_malloc_addr, alignment);

    assert(((uintptr_t) aligned_addr + (uintptr_t) size) <=

    ((uintptr_t) real_malloc_addr + (uintptr_t) aligned_size),

    "JavaThread alignment code overflowed allocated storage");

    if (TraceBiasedLocking) {

    if (aligned_addr != real_malloc_addr)

    tty->print_cr("Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT,

    real_malloc_addr, aligned_addr);

    }

    ((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr;

    return aligned_addr;

    } else {

    return throw_excpt? AllocateHeap(size, flags, CURRENT_PC)

    : os::malloc(size, flags, CURRENT_PC);

    }

    }

    如果开启了偏移锁,在创建线程时,线程地址会进行对齐处理,保证低10位为0

    实例数据

    实例数据中主要包括对象的各种成员变量,包括基本类型和引用类型;static类型的变量会放到java/lang/Class中,而不会放到实例数据中;

    对于引用类型的成员(包括string),存储的指针;对于基本类型,直接存储内容;通常会将基本类型存储在一起,引用类型存储在一起;

    例如类Test的成员定义如下:

    private static Test t1=new Test();

    private Test t2;

    private int a=5;

    private Integer b=7;

    private String c="112";

    private BigDecimal d=new BigDecimal("5");

    private long e=9l;

    2b846de4b2a48e40d2872aad736b1c29.png

    body.png

    可以看到long e、int a为基本类型,存储在一起;其它的引用类型存储在一起;int占用4bytes,不足8bytes,自动补足到8bytes;

    补充知识:java的对象物理结构,以及对象头中MarkWord与锁的关系

    java 对象头

    我们都知道,Java对象存储在堆(Heap)内存。那么一个Java对象到底包含什么呢?概括起来分为对象头、对象体和对齐字节。

    如下图所示:

    95456ba2e82f83f8df5eb00b2bbdc233.png

    对象的几个部分的作用:

    1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;

    2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;

    3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

    4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;

    5.对齐字是为了减少堆内存的碎片空间(不一定准确)。

    了解了对象的总体结构,接下来深入地了解对象头的三个部分。

    一、Mark Word(标记字)

    8b88cae423ff234b1bb26eb4e07281ad.png

    以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

    lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:

    d2bece41b6f3c979a68cd18b96b28c6e.png

    biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。

    age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

    identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。

    thread:持有偏向锁的线程ID。

    epoch:偏向锁的时间戳。

    ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。

    ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

    9032313722d1aca0157c9c8e425bf9d3.png

    二、Klass Word(类指针)

    这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

    如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。

    开启该选项后,下列指针将压缩至32位:

    每个Class的属性指针(即静态变量)

    每个对象的属性指针(即对象变量)

    普通对象数组的每个元素指针

    当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。

    三、数组长度

    如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。

    64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

    以上这篇浅谈java对象结构 对象头 Markword就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

    展开全文
  • 浅谈java对象结构 对象头 Markword 这篇文章主要介绍了浅谈java对象结构 ...| 类型 | 32位JVM | 64位JVM| | ------ ---- | ------------| --------- | | markword | 32bit | 64bit | | 类型指针 | 32bit |64bit ,开

    浅谈java对象结构 对象头 Markword

    这篇文章主要介绍了浅谈java对象结构 对象头 Markword,具有很好的参考价值,希望对大家有所帮助

    概述

    对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度;

    | 类型 | 32位JVM | 64位JVM|
    | ------ ---- | ------------| --------- |
    | markword | 32bit | 64bit |
    | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit |
    | 数组长度 | 32bit |32bit |

    header.png

    compressed_header.png

    可以看到

    未开启指针压缩时,markword占用8bytes,类型指针占用8bytes,共占用16bytes;

    开启指针压缩时(默认开启指针压缩),markword占用8bytes,类型指针占用4bytes,但由于java内存地址按照8bytes对齐,长度必须是8的倍数,因此会从12bytes补全到16bytes;

    数组长度为4bytes,同样会进行对齐,补足到8bytes;

    另外从上面的截图可以看到,开启指针压缩之后,对象类型指针为0xf800c005,但实际的类型指针为0x7c0060028;那么指针是如何压缩的呢?

    实际上由于java地址一定是8的倍数,因此将0xf800c005*8即可得到实际的指针0x7c0060028,关于指针压缩的更多知识可参考官方文档。

    markword结构

    markword的结构,定义在markOop.hpp文件:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    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)

    [ptr    | 00] locked    ptr points to real header on stack

    [header  | 0 | 01] unlocked   regular object header

    [ptr    | 10] monitor   inflated lock (header is wapped out)

    [ptr    | 11] marked    used by markSweep to mark an object

    由于目前基本都在使用64位JVM,此处不再对32位的结构进行详细说明:

    偏向锁标识位 锁标识位 锁状态 存储内容
    0 01 未锁定 hash code(31),年龄(4)
    1 01 偏向锁 线程ID(54),时间戳(2),年龄(4)
    00 轻量级锁 栈中锁记录的指针(64)
    10 重量级锁 monitor的指针(64)
    11 GC标记 空,不需要记录信息

    此处,有几点要注意:

    如果对象没有重写hashcode方法,那么默认是调用os::random产生hashcode,可以通过System.identityHashCode获取;os::random产生hashcode的规则为:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存储;另外一旦生成了hashcode,JVM会将其记录在markword中;

    GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15;

    当处于轻量级锁、重量级锁时,记录的对象指针,根据JVM的说明,此时认为指针仍然是64位,最低两位假定为0;当处于偏向锁时,记录的为获得偏向锁的线程指针,该指针也是64位;

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    We assume that stack/thread pointers have the lowest two bits cleared.

    ObjectMonitor* monitor() const {

     assert(has_monitor(), "check");

     // Use xor instead of &~ to provide one extra tag-bit check.

     return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右两位为10,因此异或之后最右两位为0

     }

    JavaThread* biased_locker() const {

     assert(has_bias_pattern(), "should not call this otherwise");

     return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));

    //~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)为11111111111111111111110010000000,计算后的结果中,低10位全部为0;

     }

    由于java中内存地址都是8的倍数,因此可以理解为最低3bit为0,因此假设轻量级和重量级锁的最低2位为0是成立的;但为什么偏向锁的最低10位都是0?查看markOop.hpp文件,发现有这么一句话:

    1

    2

    3

    4

    // Alignment of JavaThread pointers encoded in object header required by biased locking

     enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits)

    //epoch_shift+epoch_bits=10

     };

    thread.hpp中重载了operator new:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    void* operator new(size_t size) { return allocate(size, true); }

     

    // ======= Thread ========

    // Support for forcing alignment of thread objects for biased locking

    void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) {

     if (UseBiasedLocking) {

     const int alignment = markOopDesc::biased_lock_alignment;//10

     size_t aligned_size = size + (alignment - sizeof(intptr_t));

     void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC)

               : os::malloc(aligned_size, flags, CURRENT_PC);

     void* aligned_addr  = (void*) align_size_up((intptr_t) real_malloc_addr, alignment);

     assert(((uintptr_t) aligned_addr + (uintptr_t) size) <=

       ((uintptr_t) real_malloc_addr + (uintptr_t) aligned_size),

       "JavaThread alignment code overflowed allocated storage");

     if (TraceBiasedLocking) {

      if (aligned_addr != real_malloc_addr)

      tty->print_cr("Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT,

          real_malloc_addr, aligned_addr);

     }

     ((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr;

     return aligned_addr;

     } else {

     return throw_excpt? AllocateHeap(size, flags, CURRENT_PC)

          : os::malloc(size, flags, CURRENT_PC);

     }

    }

    如果开启了偏移锁,在创建线程时,线程地址会进行对齐处理,保证低10位为0

    实例数据

    实例数据中主要包括对象的各种成员变量,包括基本类型和引用类型;static类型的变量会放到java/lang/Class中,而不会放到实例数据中;

    对于引用类型的成员(包括string),存储的指针;对于基本类型,直接存储内容;通常会将基本类型存储在一起,引用类型存储在一起;

    例如类Test的成员定义如下:

    1

    2

    3

    4

    5

    6

    7

    private static Test t1=new Test();

    private Test t2;

    private int a=5;

    private Integer b=7;

    private String c="112";

    private BigDecimal d=new BigDecimal("5");

    private long e=9l;

    body.png

    可以看到long e、int a为基本类型,存储在一起;其它的引用类型存储在一起;int占用4bytes,不足8bytes,自动补足到8bytes;

    补充知识:java的对象物理结构,以及对象头中MarkWord与锁的关系

    java 对象头

    我们都知道,Java对象存储在堆(Heap)内存。那么一个Java对象到底包含什么呢?概括起来分为对象头、对象体和对齐字节。

    如下图所示:

    对象的几个部分的作用:

    1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;

    2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;

    3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

    4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;

    5.对齐字是为了减少堆内存的碎片空间(不一定准确)。

    了解了对象的总体结构,接下来深入地了解对象头的三个部分。

    一、Mark Word(标记字) 

    以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

    lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:

    biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。

    age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

    identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。

    thread:持有偏向锁的线程ID。

    epoch:偏向锁的时间戳。

    ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。

    ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

    二、Klass Word(类指针)

    这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

    如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。

    开启该选项后,下列指针将压缩至32位:

    每个Class的属性指针(即静态变量)

    每个对象的属性指针(即对象变量)

    普通对象数组的每个元素指针

    当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。

    三、数组长度

    如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。

    64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

    展开全文
  • java对象结构 对象头 Markword

    万次阅读 2018-08-23 17:45:31
    概述 对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,...| 类型 | 32位JVM | 64位JVM| | ------ ---- | ------------| --------- | | markword | 32bit | 64bit | | 类型指针 |...

    https://www.jianshu.com/p/ec28e3a59e80

    概述

    对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度;

    | 类型 | 32位JVM | 64位JVM|
    | ------ ---- | ------------| --------- |
    | markword | 32bit | 64bit |
    | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit |
    | 数组长度 | 32bit |32bit |

    header.png

    compressed_header.png

    可以看到

    • 开启指针压缩时,markword占用8bytes,类型指针占用8bytes,共占用16bytes;
    • 未开启指针压缩时,markword占用8bytes,类型指针占用4bytes,但由于java内存地址按照8bytes对齐,长度必须是8的倍数,因此会从12bytes补全到16bytes;
    • 数组长度为4bytes,同样会进行对齐,补足到8bytes;

    另外从上面的截图可以看到,开启指针压缩之后,对象类型指针为0xf800c005,但实际的类型指针为0x7c0060028;那么指针是如何压缩的呢?实际上由于java地址一定是8的倍数,因此将0xf800c005*8即可得到实际的指针0x7c0060028,关于指针压缩的更多知识可参考官方文档

    markword结构

    markword的结构,定义在markOop.hpp文件:

      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)
     [ptr             | 00]  locked             ptr points to real header on stack
     [header      | 0 | 01]  unlocked           regular object header
     [ptr             | 10]  monitor            inflated lock (header is wapped out)
     [ptr             | 11]  marked             used by markSweep to mark an object
                                                  
    

    由于目前基本都在使用64位JVM,此处不再对32位的结构进行详细说明:

    偏向锁标识位 锁标识位 锁状态 存储内容
    0 01 未锁定 hash code(31),年龄(4)
    1 01 偏向锁 线程ID(54),时间戳(2),年龄(4)
    00 轻量级锁 栈中锁记录的指针(64)
    10 重量级锁 monitor的指针(64)
    11 GC标记 空,不需要记录信息

    此处,有几点要注意:

    • 如果对象没有重写hashcode方法,那么默认是调用os::random产生hashcode,可以通过System.identityHashCode获取;os::random产生hashcode的规则为:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存储;另外一旦生成了hashcode,JVM会将其记录在markword中;
    • GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15;
    • 当处于轻量级锁、重量级锁时,记录的对象指针,根据JVM的说明,此时认为指针仍然是64位,最低两位假定为0;当处于偏向锁时,记录的为获得偏向锁的线程指针,该指针也是64位;
     We assume that stack/thread pointers have the lowest two bits cleared.
    ObjectMonitor* monitor() const {
        assert(has_monitor(), "check");
        // Use xor instead of &~ to provide one extra tag-bit check.
        return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右两位为10,因此异或之后最右两位为0
      }
    JavaThread* biased_locker() const {
        assert(has_bias_pattern(), "should not call this otherwise");
        return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));
    //~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)为11111111111111111111110010000000,计算后的结果中,低10位全部为0;
      }
    

    由于java中内存地址都是8的倍数,因此可以理解为最低3bit为0,因此假设轻量级和重量级锁的最低2位为0是成立的;但为什么偏向锁的最低10位都是0?查看markOop.hpp文件,发现有这么一句话:

    // Alignment of JavaThread pointers encoded in object header required by biased locking
      enum { biased_lock_alignment    = 2 << (epoch_shift + epoch_bits)
    //epoch_shift+epoch_bits=10
      };
    

    thread.hpp中重载了operator new:

    void* operator new(size_t size) { return allocate(size, true); }
    
    // ======= Thread ========
    // Support for forcing alignment of thread objects for biased locking
    void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) {
      if (UseBiasedLocking) {
        const int alignment = markOopDesc::biased_lock_alignment;//10
        size_t aligned_size = size + (alignment - sizeof(intptr_t));
        void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC)
                                              : os::malloc(aligned_size, flags, CURRENT_PC);
        void* aligned_addr     = (void*) align_size_up((intptr_t) real_malloc_addr, alignment);
        assert(((uintptr_t) aligned_addr + (uintptr_t) size) <=
               ((uintptr_t) real_malloc_addr + (uintptr_t) aligned_size),
               "JavaThread alignment code overflowed allocated storage");
        if (TraceBiasedLocking) {
          if (aligned_addr != real_malloc_addr)
            tty->print_cr("Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT,
                          real_malloc_addr, aligned_addr);
        }
        ((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr;
        return aligned_addr;
      } else {
        return throw_excpt? AllocateHeap(size, flags, CURRENT_PC)
                           : os::malloc(size, flags, CURRENT_PC);
      }
    }
    

    如果开启了偏移锁,在创建线程时,线程地址会进行对齐处理,保证低10位为0

    实例数据

    实例数据中主要包括对象的各种成员变量,包括基本类型和引用类型;static类型的变量会放到java/lang/Class中,而不会放到实例数据中;
    对于引用类型的成员(包括string),存储的指针;对于基本类型,直接存储内容;通常会将基本类型存储在一起,引用类型存储在一起;
    例如类Test的成员定义如下:

     private static Test t1=new Test();
     private Test t2;
     private int a=5;
     private Integer b=7;
     private String c="112";
     private BigDecimal d=new BigDecimal("5");
     private long e=9l;
    

    body.png

    可以看到long e、int a为基本类型,存储在一起;其它的引用类型存储在一起;int占用4bytes,不足8bytes,自动补足到8bytes;



    作者:allanYan
    链接:https://www.jianshu.com/p/ec28e3a59e80
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    展开全文
  • 在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件,以下我们使用64位JDK示例 1.对象布局的...

    由于Java面向对象的思想,在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能 。在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件,以下我们使用64位JDK示例

    1.对象布局的总体结构

    2.获取一个对象布局实例

    1.首先在maven项目中 引入查看对象布局的神器

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

    2.调用ClassLayout.parseInstance().toPrintable()

    public class Main{
        public static void main(String[] args) throws InterruptedException {
            L l = new L();  //new 一个对象 
            System.out.println(ClassLayout.parseInstance(l).toPrintable());//输出 l对象 的布局
        }
    }
    //对象类
    class L{
        private boolean myboolean = true;
    }

    运行后输出:

     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)                           f0 e4 2c 11 (11110000 11100100 00101100 00010001) (288154864)
         12     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
         16     1   boolean L.myboolean                               true
         17     7           (loss due to the next object alignment)
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

    对象头所占用的内存大小为16*8bit=128bit。如果大家自己动手去打印输出,可能得到的结果是96bit,这是因为我关闭了指针压缩。jdk8版本是默认开启指针压缩的,可以通过配置vm参数关闭指针压缩。关于更多压缩指针访问JAVA文档:官网

    关闭指针压缩        -XX:-UseCompressedOops 

    开启指针压缩之后,再看对象的内存布局:

     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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
         12     1   boolean L.myboolean                               true
         13     3           (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
    • OFFSET:偏移地址,单位字节;
    • SIZE:占用的内存大小,单位为字节;
    • TYPE DESCRIPTION:类型描述,其中object header为对象头;
    • VALUE:对应内存中当前存储的值;

    开启指针压缩可以减少对象的内存使用。因此,开启指针压缩,理论上来讲,大约能节省百分之五十的内存。jdk8及以后版本已经默认开启指针压缩,无需配置。

    普通的对象获取到的对象头结构为:

    |--------------------------------------------------------------|
    |                     Object Header (128 bits)                 |
    |------------------------------------|-------------------------|
    |        Mark Word (64 bits)         | Klass pointer (64 bits) |
    |------------------------------------|-------------------------|

    普通对象压缩后获取结构:

    |--------------------------------------------------------------|
    |                     Object Header (96 bits)                  |
    |------------------------------------|-------------------------|
    |        Mark Word (64 bits)         | Klass pointer (32 bits) |
    |------------------------------------|-------------------------|

    数组对象获取到的对象头结构为:

    |---------------------------------------------------------------------------------|
    |                                 Object Header (128 bits)                        |
    |--------------------------------|-----------------------|------------------------|
    |        Mark Word(64bits)       | Klass pointer(32bits) |  array length(32bits)  |
    |--------------------------------|-----------------------|------------------------|
     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)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
         16    20    int [I.<elements>                             N/A
         36     4        (loss due to the next object alignment)
    Instance size: 40 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

    3.对象头的组成

    我们先了解一下,一个JAVA对象的存储结构。在Hotspot虚拟机中,对象在内存中的存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding) 在我们刚刚打印的结果中可以这样归类:

     OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
          0     4           (object header)    //markword             01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4           (object header)    //markword             00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4           (object header)   //klass pointer 类元数据 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
         12     1   boolean L.myboolean                               true    // Instance Data 对象实际的数据
         13     3           (loss due to the next object alignment)            //Padding 对齐填充数据

    1.Mark Word

    这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。 为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下:

    de1771b4fbc358528e58399bdc2c9903.png

    其中各部分的含义如下: lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。 通过倒数三位数 我们可以判断出锁的类型

    enum {  locked_value                 = 0, // 0 00 轻量级锁
             unlocked_value           = 1,// 0 01 无锁
             monitor_value            = 2,// 0 10 重量级锁
             marked_value             = 3,// 0 11 gc标志
             biased_lock_pattern      = 5 // 1 01 偏向锁
      };
    通过内存信息分析锁状态

    写一个synchronized加锁的demo分析锁状态 接着,我们再看一下,使用synchronized加锁情况下对象的内存信息,通过对象头分析锁状态。

    代码:

    public class Main{
        public static void main(String[] args) throws InterruptedException {
            L l = new L();
            Runnable RUNNABLE = () -> {
                while (!Thread.interrupted()) {
                    synchronized (l) {
                        String SPLITE_STR = "===========================================";
                        System.out.println(SPLITE_STR);
                        System.out.println(ClassLayout.parseInstance(l).toPrintable());
                        System.out.println(SPLITE_STR);
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            for (int i = 0; i < 3; i++) {
                new Thread(RUNNABLE).start();
            }
        }
    }
    
    class L{
        private boolean myboolean = true;
    }

    输出:

    ===========================================
     OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
          0     4           (object header)                           5a 97 02 c1 (01011010 10010111 00000010 11000001) (-1056794790)
          4     4           (object header)                           d7 7f 00 00 (11010111 01111111 00000000 00000000) (32727)
          8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
         12     1   boolean L.myboolean                               true
         13     3           (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
    
    ===========================================

    Mark Word为0X00007FD7C102975A 对应的2进制为: 0xb00000000 00000000 01111111 11010111 11000001 00000010 10010111 01011010 我们可以看到在第一行object header中 value=5a 对应的2进制为01011010 倒数第三位 为0表示不是偏量锁,后两位为10表示为重量锁

    enum {  locked_value                 = 0, // 0 00 轻量级锁
             unlocked_value           = 1,// 0 01 无锁
             monitor_value            = 2,// 0 10 重量级锁
             marked_value             = 3,// 0 11 gc标志
             biased_lock_pattern      = 5 // 1 01 偏向锁
      };

    例子2:

    public class Main{
        public static void main(String[] args) throws InterruptedException {
            L l = new L();
            synchronized (l) {
                Thread.sleep(1000);
                System.out.println(ClassLayout.parseInstance(l).toPrintable());
                Thread.sleep(1000);
            }     //轻量锁
        }
    }
    
    class L{
        private boolean myboolean = true;
    }

    输出:

     OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
          0     4           (object header)                           f0 18 58 00 (11110000 00011000 01011000 00000000) (5773552)
          4     4           (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
          8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
         12     1   boolean L.myboolean                               true
         13     3           (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

    对应的mark word为0x00007000005818f0 对应的2进制为0xb00000000 00000000 01110000 00000000 00000000 01011000 00011000 11110000 根据末尾倒数第三位为0 表示不是偏量锁 倒数后2位为00 表示这是一个轻量锁

    enum {  locked_value                 = 0, // 0 00 轻量级锁
             unlocked_value           = 1,// 0 01 无锁
             monitor_value            = 2,// 0 10 重量级锁
             marked_value             = 3,// 0 11 gc标志
             biased_lock_pattern      = 5 // 1 01 偏向锁
      };

    你可能会有疑问mark word = 0x00007000005818f0是怎么算出来的, 根据前64位的value倒序排列拼成的串就是mark word 例子:

     OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
          0     4           (object header)                           f0 18 58 00 (11110000 00011000 01011000 00000000) (5773552)
          4     4           (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
          8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
         12     1   boolean L.myboolean                               true
         13     3           (loss due to the next object alignment)

    Mark word 串为 前64位倒序排列为:00000000 00000000 01110000 00000000 00000000 01011000 00011000 11110000 转换为16进制为 00007000005818f0

    2.Klass Pointer

    即对象指向它的元数据的指针,虚拟机通过这个指针来确定是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针(通过句柄池访问)。

    简单引申一下对象的访问方式,我们创建对象的目的就是为了使用它。所以我们的Java程序在运行时会通过虚拟机栈中本地变量表的reference数据来操作堆上对象。但是reference只是JVM中规范的一个指向对象的引用,那这个引用如何去定位到具体的对象呢?因此,不同的虚拟机可以实现不同的定位方式。主要有两种:句柄池和直接指针。

    2.1 使用句柄访问

    会在堆中开辟一块内存作为句柄池,句柄中储存了对象实例数据(属性值结构体)的内存地址,访问类型数据的内存地址(类信息,方法类型信息),对象实例数据一般也在heap中开辟,类型数据一般储存在方法区中。

    优点:reference存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要改变。 缺点:增加了一次指针定位的时间开销。

    8c1906c99ec6dbec204f5caef78d5cb1.png
    2.2 使用指针访问

    指针访问方式指reference中直接储存对象在heap中的内存地址,但对应的类型数据访问地址需要在实例中存储。

    优点:节省了一次指针定位的开销。 缺点:在对象被移动时(如进行GC后的内存重新排列),reference本身需要被修改。

    8cf66353626c8c71b71d6747fe4631e7.png

    总结:

    通过句柄池访问的话,对象的类型指针是不需要存在于对象头中的,但是目前大部分的虚拟机实现都是采用直接指针方式访问。此外如果对象为JAVA数组的话,那么在对象头中还会存在一部分数据来标识数组长度,否则JVM可以查看普通对象的元数据信息就可以知道其大小,看数组对象却不行

    3. 对齐填充字节

    因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8bit的倍数,就不特别介绍了

    4.JVM升级锁的过程

    1,当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。

    2,当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。

    3,当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。

    4,当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。

    5,偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。

    6,轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。

    7,自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。

    总结:本章节主要介绍了对象布局包含对象头,对象实例数据,和对齐数据.并且介绍了对象头中包含的信息和解析方法 更多内容请持续关注公众号:java宝典

    关注公众号:java宝典

    bf494edb424f113d0c9e50dbbbfac8dd.png
    展开全文
  • 在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件,以下我们使用64位JDK示例1.对象布局的...
  • Java对象结构

    2019-02-15 13:52:21
    概述 对象实例由对象头、实例数据...| 类型 | 32位JVM | 64位JVM| | ------ ---- | ------------| --------- | | markword | 32bit | 64bit | | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit | | 数组长度 | ...
  • 对象头⽤于存储对象的元数据信息 Mark Word 部分数据的⻓度在32位和64位虚拟机(未开启压缩指针)中分别为32bit和64bit,存储对象⾃身的运⾏时数据如哈希值等。Mark Word⼀般被设计为⾮固定的数据结构,以便存储更...
  • 前言 在HotSpot虚拟机中,对象在内存中存储的布局分为3块区域: 对象头 ...因为对象头占用的空间比较小,所以空间不是固定的数据结构,是随着状态不同,而存储的内容不同: 存储内容 标志 状...
  • 我们经常会说到synchronized是一把重量级的锁,难道是因为这把锁有几斤?...以下是64位JVM下的对象结构描述: 对象结构 可以看到,在Java中,对象的结构主要分为:对象头,实例数据以及填充数据。...
  • 在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件,以下我们使用64位JDK示例 1.对象布局的...
  • 因为JVM虚拟机为HotSpot,以下谈的都是在64位HotSpot虚拟机中 对象内存布局 对象的内存布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding) 注:数组对象中对象头部分多了...
  • 1.对象头用于存储对象 的元数据信息: Mark Word部分数据的长度在32位和64位机器(未开启压缩指针)中分别为32bit和64bit,存储对象自身的运行时数据如哈希值等。Mark Word一般被设计为非固定的数据结构,以便存储...
  • 普通对象的结构如下,按64位机器的长度计算1. 对象头(_mark), 8个字节2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节3. 数据区4.Padding(内存对齐),按照8的倍数对齐数组对象结构是1. ...
  • java对象在内存中的结构

    万次阅读 2020-09-02 17:25:09
    JVM中,一般来说,Java对象都是分配在堆中,那么对象在堆中长什么样呢? 对象头包含以下几个部分: MarkWord:包含对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode、分代年龄等。 Class Pointer...
  • java对象在堆中主要分为四部分结构, 分别是对象头MarkWord, 对象指针ClassWord, 实例对象(如果对象是数组的话, 这里需要再分成两部分, 多了一个存储数组长度的数据位), 8字节对齐位. 下面以64位JVM为例, 分析内存...
  • 在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件,以下我们使用64位JDK示例1.对象布局的...
  • [size=large]一、对象的内存布局[/size] ... 对象头:对象头结构在32位JVM与64位JVM中的实现细节是不同的 32bit: [img]http://dl2.iteye.com/upload/attachment/0114/8382/c82dbe73-fdb5-3a24-ba94-a8adc4d0f...
  • JVM对象的内存布局

    2018-04-09 22:59:26
    Mark Word 非固定结构,32位虚拟机占32位,64位虚拟机占64位(占64位条件是没有开启压缩指针) 类型指针,并非一定要有,与对象访问方法有关 如果是数组,无法通过元数据信息确定java对象大小,这个时候就需要记录...
  • 在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件,以下我们使用64位JDK示例 1.对象布局的...
  • 在学习并发编程知识 synchronized 时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解 java 对象头是我们深入了解 synchronized 的前提条件,以下我们使用 64 JDK 示例 ...
  • 1、Java对象的内存结构 ...2)64位系统及64位JVM:开启指针压缩,Class指针4字节 + Mark Word空间8字节 3)32位系统,使用new Object(),JVM分配8个字节的空间 4)32位系统,使用new Integet(),对象...
  • Java对象模型 - Klass Word一、OOP-Klass1. Klass Word: 是java对象模型中的头部信息,用来描述java类...3. 内存消耗: 64位JVM的内存占用多了50%, 后期JVM为了节省空间,可以使用参数+UseCompressedOops, 开启指...
  • 原标题:Java知识进阶-程序员未接触的知识点之对象模型Klass Word-知识铺Java对象模型 - Klass Word一、...2. 占用的长度: 32位JVM ,占用为32位, 64位JVM,占用为64位。3. 内存消耗: 64位JVM的内存占用多了50%...
  • 上篇博客介绍的对象的创建过程,本文来介绍一下对象的组成结构。 在HotSpot虚拟机中,对象在内存中的布局划分为3个区域:对象头(Header),实例数据(Instance Data)以及对齐填充(Padding...在32位和64位JVM中...
  • 32位JVM的Mark Word的默认存储结构如下图所示: 在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据,如下图所示: 在64位虚拟机下,Mark Word是64bit大小的,...
  • * 普通对象的结构如下,按64位机器的长度计算 1. 对象头(_mark), 8个字节 2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节 3. 数据区 4.Padding(内存对齐),按照8的倍数对齐 ----所以在32G...

空空如也

空空如也

1 2 3
收藏数 46
精华内容 18
关键字:

java对象头结构64位jvm

java 订阅