您当前的位置: 首页 >  Java

Dongguo丶

暂无认证

  • 2浏览

    0关注

    472博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java对象内存布局

Dongguo丶 发布时间:2021-09-26 08:54:04 ,浏览量:2

对象在堆内存中布局

摘自周志明老师JVM第3版

image-20210902163833095

image-20210908174741760

对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。

对象头分为对象标记(markword,类元信息/类型指针(Class Pointer ) -java层面的

对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。 --c++层面的

对齐填充就是保证8个字节的倍数

对象头

对象头分为Mark Word 对象标记和Class Pointer 类型指针

Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位 等信息

image-20210908180119153

对象标记Mark Word

img

image-20210908175001756

默认存储对象的HashCode、分代年龄和锁标志位等信息。

​ 每个对象都有一个hashcode

​ GC垃圾回收的次数,java8中对象超过15次yonggc,进入老年代,所以占4个字节

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

如果修改为16试一试

-XX:MaxTenuringThreshold=16

image-20210908193117352

image-20210908175028641

这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。 它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

类元信息(又叫类型指针)Class Pointer

Customer customer = new Customer()

new Customer()的实例Customer就是类元信息

image-20210908175129037

对象的类型指针指向它的类元数据,虚拟机通过这个指针来确定这个对象是哪个类的实例。

对象头多大

一个对象 在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,

如果类型指针没有经过压缩,一个对象 的对象头一共是16个字节。

如果类型指针经过压缩会变为4个字节, (类型指针的压缩会在最后部分说明)

所以对象 的对象头一共是12个字节。

底层源码

oop.hpp

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;  //_mark字段是mark word
  union _metadata {   //_metadata是类指针klass pointer
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;

  // Fast access to barrier set.  Must be initialized.
  static BarrierSet* _bs;

 public:
  markOop  mark() const         { return _mark; }
  markOop* mark_addr() const    { return (markOop*) &_mark; }//c++层面的对象标记
    ...
}

对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表,

image-20210908185412641

实例数据

存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。



    org.openjdk.jol
    jol-core
    0.9

对于一个没有任何属性的对象

package com.dongguo.object;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author Dongguo
 * @date 2021/9/8 0008-18:30
 * @description:
 */
class MyObject{

        }
public class MyObjectDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        //使用ClassLayout解析object并转成可以打印的格式
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
    }
}
运行结果
com.dongguo.object.MyObject object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x00060a18
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Mark Word 占8个字节

Class Pointer占4个字节(经过压缩)

实例数据为 0个字节

对齐填充4个字节

总大小为16个字节

对于一个有属性的对象

package com.dongguo.object;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author Dongguo
 * @date 2021/9/8 0008-18:30
 * @description:
 */
class MyObject {
    int i = 5;
    char a = 'a';
}

public class MyObjectDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        //使用ClassLayout解析object并转成可以打印的格式
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
    }
}
运行结果
com.dongguo.object.MyObject object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x2000c143
 12   4    int MyObject.i                5
 16   2   char MyObject.a                a
 18   6        (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total

int 占4个字节

char占2个字节

Mark Word 占8个字节

Class Pointer占4个字节

实例数据为 6个字节 18个字节

根据8的倍数 对齐填充6个字节

总大小为24个字节

对齐填充

虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐

这部分内存按8字节补充对齐。

再说对象头的MarkWord

32位了解即可

image-20210908185712095

看一下64位的:

image-20210908185744002

markOop.hpp就有对对象头的注释介绍

// The markOop describes the header of an object.
//
// Note that the mark is not a real oop but just a word.
// It is placed in the oop hierarchy for historical reasons.
//
// 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)
//
//  - hash contains the identity hash value: largest value is
//    31 bits, see os::random().  Also, 64-bit vm's require
//    a hash value no bigger than 32 bits because they will not
//    properly generate a mask larger than that: see library_call.cpp
//    and c1_CodePatterns_sparc.cpp.
//
//  - the biased lock pattern is used to bias a lock toward a given
//    thread. When this pattern is set in the low three bits, the lock
//    is either biased toward a given thread or "anonymously" biased,
//    indicating that it is possible for it to be biased. When the
//    lock is biased toward a given thread, locking and unlocking can
//    be performed by that thread without using atomic operations.
//    When a lock's bias is revoked, it reverts back to the normal
//    locking scheme described below.
//
//    Note that we are overloading the meaning of the "unlocked" state
//    of the header. Because we steal a bit from the age we can
//    guarantee that the bias pattern will never be seen for a truly
//    unlocked object.
//
//    Note also that the biased state contains the age bits normally
//    contained in the object header. Large increases in scavenge
//    times were seen when these bits were absent and an arbitrary age
//    assigned to all biased objects, because they tended to consume a
//    significant fraction of the eden semispaces and were not
//    promoted promptly, causing an increase in the amount of copying
//    performed. The runtime system aligns all JavaThread* pointers to
//    a very large value (currently 128 bytes (32bVM) or 256 bytes (64bVM))
//    to make room for the age bits & the epoch bits (used in support of
//    biased locking), and for the CMS "freeness" bit in the 64bVM (+COOPs).
//
//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
//
//  - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
//    [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
//                                               not valid at any other time
//
//    We assume that stack/thread pointers have the lowest two bits cleared.

hash: 保存对象的哈希码 age: 保存对象的分代年龄 biased_lock: 偏向锁标识位 lock: 锁状态标识位 JavaThread* :保存持有偏向锁的线程ID epoch: 保存偏向时间戳

nornal object 无锁状态

markword(64位)详细分布图,

对象布局、GC回收和后面的锁升级就是 对象标记MarkWord里面标志位的变化

image-20210908185828484

Object o = new Object()在内存中占了多少字节?

一般而言JDK8按照默认情况下,new一个对象占多少内存空间

JOL证明

JOL官网http://openjdk.java.net/projects/code-tools/jol/



    org.openjdk.jol
    jol-core
    0.16

使用jol

package com.dongguo.object;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author Dongguo
 * @date 2021/9/8 0008-19:09
 * @description:
 */
public class ObjectHeadDemo {
    public static void main(String[] args) {
        Object object = new Object();
        //使用ClassLayout解析object并转成可以打印的格式
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}
运行结果
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

OFFSET 偏移量,也就是到这个字段位置所占用的byte数 SIZE 后面类型的字节大小 TYPE 是Class中定义的类型 DESCRIPTION DESCRIPTION是类型的描述 VALUE VALUE是TYPE在内存中的值

Mark Word 占8个字节

Class Pointer占4个字节(Class Pointer原本占8个字节 经过压缩为4个字节)

实例数据为 0个字节(Object没有属性(Field)数据信息)

对齐填充4个字节

总大小为16个字节

Class Pointer类型指针原本是8个字节 为什么会压缩到4个字节呢

查看JVM设置的参数

java -XX:+PrintCommandLineFlags -version
    
-XX:InitialHeapSize=265410752 -XX:MaxHeapSize=41046572032 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividu
alAllocation -XX:+UseParallelGC
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)

-XX:+UseCompressedClassPointers -XX:+UseCompressedOops

JVM默认为了提高性能,节约内存

-XX:+UseCompressedClassPointers 默认 开启压缩类型指针

类型指针从8个字节压缩为了4个字节

上述表示开启了类型指针的压缩,以节约空间,假如不加压缩???

-XX:-UseCompressedClassPointers

image-20210908204007505

package com.dongguo.object;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author Dongguo
 * @date 2021/9/8 0008-19:09
 * @description:
 */
public class ObjectHeadDemo {
    public static void main(String[] args) {
        Object object = new Object();
        //使用ClassLayout解析object并转成可以打印的格式
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}
运行结果
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)                           00 1c 59 ae (00000000 00011100 01011001 10101110) (-1369891840)
     12     4        (object header)                           f0 02 00 00 (11110000 00000010 00000000 00000000) (752)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

对象标记8个字节

类型指针8个字节

实例数据0个字节

对齐填充0个字节

一共16个字节

换成其他对象试试
package com.dongguo.object;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author Dongguo
 * @date 2021/9/8 0008-18:30
 * @description: -XX:-UseCompressedClassPointers
 */
class MyObject {
    int i = 5;
    char a = 'a';
    long l =99;
}

public class MyObjectDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        //使用ClassLayout解析object并转成可以打印的格式
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
    }
}
运行结果
com.dongguo.object.MyObject 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)                           c8 35 e9 e8 (11001000 00110101 11101001 11101000) (-387369528)
     12     4        (object header)                           66 01 00 00 (01100110 00000001 00000000 00000000) (358)
     16     8   long MyObject.l                                99
     24     4    int MyObject.i                                5
     28     2   char MyObject.a                                a
     30     2        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

类型指针占8个字节

image-20210908204541617

package com.dongguo.object;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author Dongguo
 * @date 2021/9/8 0008-18:30
 * @description: XX:+UseCompressedClassPointers
 */
class MyObject {
    int i = 5;
    char a = 'a';
    long l =99;
}

public class MyObjectDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        //使用ClassLayout解析object并转成可以打印的格式
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
    }
}
运行结果
com.dongguo.object.MyObject 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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int MyObject.i                                5
     16     8   long MyObject.l                                99
     24     2   char MyObject.a                                a
     26     6        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total

类型指针占4个字节

所以new Object()

当开启压缩类型指针(默认开启) 对象标记8个字节,类型指针4个字节,对齐填充4个字节 共16个字节

当没有开启压缩类型指针 对象标记8个字节,类型指针8个字节 共16个字节

关注
打赏
1638062488
查看更多评论
立即登录/注册

微信扫码登录

0.0392s