探索jvm中的对象

前言

本文结合常用jvm-Hotspot和常用的内存区域Java堆,来探讨一下普通Java对象创建过程中,在堆中的分配布局和访问过程。如需要了解jvm的内存模型请移步:JVM内存模型-详解

Java对象的创建过程

简单流程如下图:
普通Java对象创建流程

内存分配

其中,在为对象分配内存的时候,依赖于jvm采用哪种垃圾收集器,并且取决于该垃圾收集器是否带有压缩空间整理的能力。根据堆内存是否规整分别采用“指针对撞”和“空闲列表”算法。例如,当使用Serial、ParNew等带有压缩整理过程的收集器时,系统采用的分配算法是指针碰撞。当使用CMS这种基于清除算法的收集器时,一般采用空闲列表来分配内存。

指针碰撞和空闲列表:

指针碰撞

“指针碰撞”(Bump The Pointer):假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
指针碰撞

空闲列表

“空闲列表”(FreeList):如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
空闲列表

初始化对象基本信息

对象在堆中的布局可以划分为三个部分:对象头(Herder)、实例数据(Instance Data)、和对齐填充(Padding)。
这里提到的对象基本信息,主要是解释对象头(Object Header)。其中包含Mark Word和类型指针。
其中,Mark Word 是存储对象自身的运行时数据,如哈希码(Hash Code)、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等信息。类型指针指向的对象的类型元数据,表明这个对象是哪个类的实例。在我们用到反射的时候,就是从对象的类型指针找到对应的类。

初始化对象属性信息

在类的基本信息初始化完成后,从jvm的角度一个新的对象已经产生,但是从java程序来说对象创建才刚刚开始,即刚刚运行构造函数,在构造函数运行前,所有的字段都默认为0值,直至运行完< init>()方法,才算构建出一个真正的Java对象。实例数据部分是对象真正存储有效数据的部分,即我们代码中定义的字段,包括从父类继承的还有子类定义的字段都会记录下来。

对象的内存布局补充

JOL使用

上面我们提到Java对象在堆中的存储布局分为三部分:对象头(Herder)、实例数据(Instance Data)、和对齐填充(Padding)。现在我们具体看一些这三个部分。
首先,我们引入一个工具,openjdk提供的JOL(Java Object Layout)
maven 依赖:

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

下面我们看一段代码:

import org.openjdk.jol.info.ClassLayout;

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

我们简单看一下一个Object对象的内存信息:
对象信息
名词解释:
OFF:即offset,偏移地址,单位字节;
SZ:即size,占用的内存大小,单位为字节;
TYPE DESCRIPTION: 类型描述,其中object header为对象头;

  1. object header: mark:mark word;
  2. object header: class:类指针;
  3. object alignment gap:为对齐的偏移;

VALUE:对应内存中存的值;
Instance size:该对象字节数值大小,现在这个空的Object对象占16字节;

可以看到header包含mark word和class信息,其中mark占8个字节,类信息占4个字节,所以对象的header信息一共占12个字节。偏移4字节,一共16字节。

对齐填充

前面已经对Java对象的头信息和实例信息有个简单的说明了,那么什么是对齐填充呢?
对齐填充不是必然存在的,也没有特别的含义,仅仅是作为占位符来方便jvm的内存管理,jvm自动内存管理系统要求,任何对象的大小都必须是8字节的整数倍,因此,不足的部分需要补齐。例如上文一个空的Object对象实际使用的是12字节,为了满足这个设计,需要偏移4个字节补至16字节。
问题思考:
1、一个仅拥有一个int 类型字段的对象,占几个字节?
2、一个仅用于一个String类型字段对象,并且该字段初始值是"Hello World!",该对象占有几个字节?

JOL常用方法:

计算对象的大小(单位为字节):ClassLayout.parseInstance(obj).instanceSize()
查看对象内部信息: ClassLayout.parseInstance(obj).toPrintable()
查看对象外部信息:包括引用的对象:GraphLayout.parseInstance(obj).toPrintable()
查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()

写在最后

其实对象的头信息很复杂,等有时间写到多线程的时候,提及各种锁的概念的时候再进行刨析吧。最后说一句:开源自己,让编码更美好~

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值