Java对象内存布局

Posted King Gigi.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java对象内存布局相关的知识,希望对你有一定的参考价值。

文章目录

Object object = new Object() 谈谈你对这句话的理解?一般而言JDK8按照默认情况下,new一个对象占多少内存空间

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:

  • 对象头

  • 实例数据

  • 对齐填充

对象头分为对象标记(markOop)类元信息 (klassOop)

类元信息存储的是指向该对象 类元数据(klass)的首地址。

引出问题

public class Demo01 
    public static void main(String[] args) 
        Object o = new Object();//?new 一个对象,内存占多少,记录在哪里?

        System.out.println(o.hashCode());//356573597,这个hashCode又是记录在哪里的

        synchronized (o)//加锁信息又是记录在哪里的

        
        System.gc();//手动垃圾收集中,15次可以从新生代到养老区,那这个次数又是记录在哪里的
    

刚刚几个问题都保存在对象标记

贴一张对象内部结构图:

1、对象头

对象标记Mark Word

用于存储自身运行时的数据例如CG标志位、哈希码、锁状态等信息

在64位系统中,MarkWord占了8个字节,类型指针占了8个字节,一共是16个字节

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

所谓的类元信息(类对象指针)其实就可以说是模板,用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例

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

数组长度(Array Length)(可选)

如果对象是一个Java数组,那么该字段必须存在,用于存放数组长度,占用32位字节

2、实例数据(对象体)

实例数据:存放类的属性(Field)信息,包括父类的属性信息,这部分内存按4字节对齐

3、对齐填充

用来保证Java对象所占内存字节数为8字节的倍数,

对齐填充:虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐。对象头本身是8的倍数,当对象的实例数据不是8 的倍数时,需要使用填充数据来保证8 字节的对齐

来个案例,对象头16+实例数据5+对齐填充3=24字节

4、指针压缩

对于对象指针来说,如果JVM中的对象数量过多,使用64位的指针将浪费大量内存(比32位多浪费50%),为了节约内存可以使用选项+UseCompressedOops 开启指针压缩

以下类型指针会从64位压缩到32位:

  • Class对象的属性指针(静态变量)
  • Object对象的属性值指针(成员变量)
  • 普通对象数组的元素指针

5、再聊对象头的MarkWord

底层源码涉及到的一些字段:

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

6、JOL分析对象在Java虚拟机中的大小和布局

导入依赖

Demo测试:

public class JolDemo 
    public static void main(String[] args) 
        //Vm的细节详细情况
        System.out.println(VM.current().details());
        //所有的对象分配的字节都是8的整数倍
        System.out.println(VM.current().objectAlignment());
    
    /**
     * 运行情况:
     * # Running 64-bit HotSpot VM.
     * # Using compressed oop with 3-bit shift.
     * # Using compressed klass with 3-bit shift.
     * # Objects are 8 bytes aligned.
     * # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
     * # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
     *
     * 8
     *
     * 进程已结束,退出代码0
     */

看看Object 类:

Object obj = new Object()

public class JolDemo 
    public static void main(String[] args) 
        Object o = new Object();//----------新建一个Object对象就是  16bytes
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    

为什么类型指针是4字节?之前不都是说是8字节的吗?(因为压缩指针默认开启了)

再来看看自定义的类:

public class JolDemo 
    public static void main(String[] args) 
        Customer c1 = new Customer();
        System.out.println(ClassLayout.parseInstance(c1).toPrintable());
    


//只有对象头,没有实例数据,依然是16byte
class Customer


/**
 * 运行结果:
 * com.hh.demo.Customer 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 f8 (01000011 11000001 00000000 11111000) (-134168253)
 *      12     4        (loss due to the next object alignment)
 * Instance size: 16 bytes    
 * Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 *
 *
 * 进程已结束,退出代码0
 */
public class JolDemo 
    public static void main(String[] args) 
        Customer c1 = new Customer();
        System.out.println(ClassLayout.parseInstance(c1).toPrintable());
    


//有了对象头,且有实例数据(int+boolean),它进行了对齐填充,到了24byte
class Customer
    int id;
    boolean flag = false;

/**
 * 运行结果:
 * com.hh.demo.Customer 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 f8 (01000011 11000001 00000000 11111000) (-134168253)
 *      12     4       int Customer.id                               0
 *      16     1   boolean Customer.flag                             false
 *      17     7           (loss due to the next object alignment)
 * Instance size: 24 bytes
 * Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
 *
 *
 * 进程已结束,退出代码0
 */

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

  • 对象分代年龄最大就是15

  • 我们假如想直接把分代最大年龄修改为16会直接报错。
-XX:MaxTenurningThreshold=16

尾巴参数说明(压缩指针相关)
压缩指针相关的命令(压缩指针是否开启对我们new一个对象是不是16字节的影响)

查看当前JVM运行参数的指令

  • java -XX:+PrintCommandLineFlags -version

压缩指针默认是开启的
(这也就解释了为什么前面的类型指针是4个字节,节约了内存空间)

假如不压缩的情况?我们手动关闭压缩指针看看?

+是开启,-就是关闭,所以指令是

  • -XX:-UseCompressedClassPointers

不管是否开启压缩指针,创建一个对象就是16字节的。(开启压缩指针后缺失的会由对齐填充补充)

Java对象的内存布局

近期在写一个C++ 和java的socket通信程序。须要把收到的字节流转存到一个对象,引申出了这个问题,查找了一些网上的资料,总结例如以下

本文仅仅包括简单java对象的内存布局,不考虑继承的情况


 Java类的一个实例在内存中包括 对象头,非静态数据成员和对齐数据


静态数据成员,方法成员为类的全部实例共享。不保存在某个对象实例里


对象头的大小取决于于JVM的实现。同一个JVM,普通对象,数组对象。内部对象的对象头大小又有差别。


基本数据类型的大小JVM规范有明白定义,但引用类型在32bit JVM下是4字节,64bit JVM下是8字节(不启用指针压缩參数UseCompressedOops)。


32bit JVM 和 64bit JVM一般都是8字节(64 bit)对齐。

 

一个64bit JVM里面一个基本对象内存布局可能例如以下所看到的

 技术分享

数组对象的对象头是24字节,除去16字节基本信息,再加4字节长度数据。4字节对齐数据。另外。对象数组的每个元素保存的是对象的引用。

非静态的内部对象的对象头也是24字节。16字节的基本信息,加上一个外部类的引用8字节,64bit JVM是8字节,32bit JVM 是4字节加上对齐数据4字节。

假设对象的成员中,含有其它对象比方数组,字符串,这个对象仅仅保存这些成员对象的引用,这些成员对象的内存并不直接包括在这个对象的内存中,在它之外。 


非静态成员在对象内存中的位置和声明顺序是一致的吗?未必


我查找到了一篇相关博客,写于2008年http://www.importnew.com/1305.html

里面提到 Sun VM(如今已经是Oracle VM了,。。)并没有依照属性声明时的顺序进行内存布局,为了节省内存採用了下面顺序

1. 双精度型(doubles)和长整型(longs)

2. 整型(ints)和浮点型(floats)

3. 短整型(shorts)和字符型(chars)

4. 布尔型(booleans)和字节型(bytes)

5. 引用类型(references)

 

这篇博客提到了5个规则

规则1:不论什么对象都是8个字节为粒度进行对齐的。

规则2:类属性依照例如以下优先级进行排列:长整型和双精度类型。整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都依照各自的单位对齐。

规则3:不同类继承关系中的成员不能混合排列。首先依照规则2处理父类中的成员。接着才是子类的成员。

规则4:当父类中最后一个成员和子类第一个成员的间隔假设不够4个字节的话,就必须扩展到4个字节的基本单位

规则5:假设子类第一个成员是一个双精度或者长整型。而且父类并没实用完8个字节。JVM会破坏规则2。依照整形(int),短整型(short)。字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。

 

所以这个规则还是比較复杂的

这篇博客写于2008年。JVM的实现非常可能已经变化,并且JVM版本号众多,了解这么多各自的规范是一件比較麻烦的事,从有用的角度来说,能够仅仅了解某个对象的布局

openJDK提供了一个工具。能够查看对象的内存布局,这篇博客里面介绍了这个工具http://blog.csdn.net/aitangyong/article/details/46416667,能够使用这个工具进行分析。


说明:

本文由giantpoplar发表于CSDN

文章地址 http://blog.csdn.net/giantpoplar/article/details/47657377

转载请保留本说明












以上是关于Java对象内存布局的主要内容,如果未能解决你的问题,请参考以下文章

JVM内存堆布局图解分析

深入理解JVM虚拟机读书笔记——对象的创建与内存布局

JVM —— Java 对象占用空间大小计算

深入理解JVM虚拟机读书笔记——对象的创建与内存布局

java源码剖析: 对象内存布局JVM锁以及优化

Java对象的内存布局